'
' *  Authors:  Benton Stark
' * 
' *  Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com) 
' *
' * Permission is hereby granted, free of charge, to any person obtaining a copy
' * of this software and associated documentation files (the "Software"), to deal
' * in the Software without restriction, including without limitation the rights
' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
' * copies of the Software, and to permit persons to whom the Software is
' * furnished to do so, subject to the following conditions:
' * 
' * The above copyright notice and this permission notice shall be included in
' * all copies or substantial portions of the Software.
' * 
' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
' * THE SOFTWARE.
' * 
' 



Imports System.Net
Imports System.Net.Sockets
Imports System.Net.Security
Imports System.Security.Cryptography
Imports System.Security.Cryptography.X509Certificates
Imports System.Security.Authentication
Imports System.IO
Imports System.Text
Imports System.Threading
Imports System.Globalization
Imports System.ComponentModel
Imports System.IO.Compression
Imports System.Diagnostics
Imports SFDL.Proxyv2.Proxy
Imports System.Text.RegularExpressions



Namespace FTP
#Region "Public/Internal Enums"

    ''' <summary>
    ''' The type of data transfer mode (e.g. Active or Passive).
    ''' </summary>
    ''' <remarks>
    ''' The default setting is Passive data transfer mode.  This mode is widely used as a
    ''' firewall friendly setting for the FTP clients operating behind a firewall.
    ''' </remarks>
    Public Enum TransferMode As Integer
        ''' <summary>
        ''' Active transfer mode.  In this mode the FTP server initiates a connection to the client when transfering data.
        ''' </summary>
        ''' <remarks>This transfer mode may not work when the FTP client is behind a firewall and is accessing a remote FTP server.</remarks>
        Active
        ''' <summary>
        ''' Passive transfer mode.  In this mode the FTP client initiates a connection to the server when transfering data.
        ''' </summary>
        ''' <remarks>
        ''' This transfer mode is "firewall friendly" and generally allows an FTP client behind a firewall to access a remote FTP server.
        ''' This mode is recommended for most data transfers.
        ''' </remarks>
        Passive
    End Enum

    ''' <summary>
    ''' Enumeration representing the type of integrity algorithm used to verify the integrity of the file after transfer and storage.
    ''' </summary>
    Public Enum HashingAlgorithm As Integer
        ''' <summary>
        ''' No algorithm slected.
        ''' </summary>
        None
        ''' <summary>
        ''' Cyclic redundancy check (CRC).  A CRC can be used in a similar way as a cryptogrphic hash to detect accidental 
        ''' alteration of data during transmission or storage.  A CRC value does not provide a value for true file integrity value
        ''' but rather can be used to detect transmission errors.  Use a cryptographic hashing function such as SHA-1 or higher
        ''' for true cryptographic integrity.
        ''' </summary>
        ''' <remarks>
        ''' It is often falsely assumed that when a message and its CRC are transmitted over an open channel, then when it arrives 
        ''' if the CRC matches the message's calculated CRC then the message cannot have been altered in transit.  A CRC value 
        ''' is produced from a cryptographic hashing algorithm but is rather just a simple computational check value.
        ''' For this reason it is recommended to use a cryptographic hashing function such as SHA-1 or higher whenever possible.
        ''' For new systems it is recommend to use the SHA-2 cryptographic hashing functions.
        ''' </remarks>
        ''' <seealso cref="Md5"/>
        ''' <seealso cref="Sha1"/>
        ''' <seealso cref="Sha256"/>
        ''' <seealso cref="Sha512"/>
        Crc32
        ''' <summary>
        ''' Message-Digest algorithm 5 (MD5).  Hashing function used to produce a 'unique' signature to detect 
        ''' alternation of data during transmission or storage.
        ''' </summary>
        ''' <remarks>
        ''' MD5 is a weak algorithm and has been show to produce collisions.  For this reason it is recommended to use SHA-1 or higher when possible.
        ''' </remarks>
        ''' <seealso cref="Crc32"/>
        ''' <seealso cref="Sha1"/>
        ''' <seealso cref="Sha256"/>
        ''' <seealso cref="Sha512"/>
        Md5
        ''' <summary>
        ''' Secure Hash Algorithm (SHA-1).  Cryptographic hash functions designed by the National Security Agency (NSA) and published by the NIST as a U.S. Federal Information Processing Standard.
        ''' </summary>
        ''' <remarks>
        ''' The SHA-1 hashing algorithm has been shown to be theoritically vunerable to a mathematical weakness therefore the SHA-2 functions are recommended on new projects.
        ''' </remarks>
        ''' <seealso cref="Md5"/>
        ''' <seealso cref="Crc32"/>
        ''' <seealso cref="Sha256"/>
        ''' <seealso cref="Sha512"/>
        Sha1
        ''' <summary>
        ''' Secure Hash Algorithm (SHA-2).  Cryptographic hash functions designed by the National Security Agency (NSA) and published by the NIST as a U.S. Federal Information Processing Standard.
        ''' </summary>
        ''' <remarks>
        ''' The SHA-1 hashing algorithm has been shown to be theoritically vunerable to a mathematical weakness therefore the SHA-2 functions are recommended on new projects.
        ''' </remarks>
        ''' <seealso cref="Md5"/>
        ''' <seealso cref="Crc32"/>
        ''' <seealso cref="Sha1"/>
        ''' <seealso cref="Sha512"/>
        Sha256
        ''' <summary>
        ''' Secure Hash Algorithm (SHA-2).  Cryptographic hash functions designed by the National Security Agency (NSA) and published by the NIST as a U.S. Federal Information Processing Standard.
        ''' </summary>
        ''' <remarks>
        ''' The SHA-1 hashing algorithm has been shown to be theoritically vunerable to a mathematical weakness therefore the SHA-2 functions are recommended on new projects.
        ''' </remarks>
        ''' <seealso cref="Md5"/>
        ''' <seealso cref="Crc32"/>
        ''' <seealso cref="Sha1"/>
        ''' <seealso cref="Sha256"/>
        Sha512
    End Enum

    ''' <summary>
    ''' Defines the possible versions of FtpSecurityProtocol.
    ''' </summary>
    Public Enum FtpSecurityProtocol As Integer
        ''' <summary>
        ''' No security protocol specified.
        ''' </summary>
        None
        ''' <summary>
        ''' Specifies Transport Layer Security (TLS) version 1.0 is required to secure communciations.  The TLS protocol is defined in IETF RFC 2246 and supercedes the SSL 3.0 protocol.
        ''' </summary>
        ''' <remarks>
        ''' The AUTH TLS command is sent to the FTP server to secure the connection.  TLS protocol is the latest version of the SSL protcol and is the security protocol that should be used whenever possible.
        ''' There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same.
        ''' </remarks>
        Tls1Explicit
        ''' <summary>
        ''' Specifies Transport Layer Security (TLS) version 1.0. or Secure Socket Layer (SSL) version 3.0 is acceptable to secure communications in explicit mode.
        ''' </summary>
        ''' <remarks>
        ''' The AUTH SSL command is sent to the FTP server to secure the connection but the security protocol is negotiated between the server and client.  
        ''' TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible.
        ''' There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same.
        ''' </remarks>
        Tls1OrSsl3Explicit
        ''' <summary>
        ''' Specifies Secure Socket Layer (SSL) version 3.0 is required to secure communications in explicit mode.  SSL 3.0 has been superseded by the TLS protocol
        ''' and is provided for backward compatibility only
        ''' </summary>
        ''' <remarks>
        ''' The AUTH SSL command is sent to the FTP server to secure the connection.  TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible.
        ''' There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same.
        ''' Some FTP server do not implement TLS or understand the command AUTH TLS.  In those situations you should specify the security
        ''' protocol Ssl3, otherwise specify Tls1.
        ''' </remarks>
        Ssl3Explicit
        ''' <summary>
        ''' Specifies Secure Socket Layer (SSL) version 2.0 is required to secure communications in explicit mode.  SSL 2.0 has been superseded by the TLS protocol
        ''' and is provided for backward compatibility only.  SSL 2.0 has several weaknesses and should only be used with legacy FTP server that require it.
        ''' </summary>
        ''' <remarks>
        ''' The AUTH SSL command is sent to the FTP server to secure the connection.  TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible.
        ''' There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same.
        ''' Some FTP server do not implement TLS or understand the command AUTH TLS.  In those situations you should specify the security
        ''' protocol Ssl3, otherwise specify Tls1.
        ''' </remarks>
        Ssl2Explicit
        ''' <summary>
        ''' Specifies Transport Layer Security (TLS) version 1.0 is required to secure communciations in explicit mode.  The TLS protocol is defined in IETF RFC 2246 and supercedes the SSL 3.0 protocol.
        ''' </summary>
        ''' <remarks>
        ''' The AUTH TLS command is sent to the FTP server to secure the connection.  TLS protocol is the latest version of the SSL protcol and is the security protocol that should be used whenever possible.
        ''' There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same.
        ''' </remarks>
        Tls1Implicit
        ''' <summary>
        ''' Specifies Transport Layer Security (TLS) version 1.0. or Secure Socket Layer (SSL) version 3.0 is acceptable to secure communications in implicit mode.
        ''' </summary>
        ''' <remarks>
        ''' The AUTH SSL command is sent to the FTP server to secure the connection but the security protocol is negotiated between the server and client.  
        ''' TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible.
        ''' There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same.
        ''' </remarks>
        Tls1OrSsl3Implicit
        ''' <summary>
        ''' Specifies Secure Socket Layer (SSL) version 3.0 is required to secure communications in implicit mode.  SSL 3.0 has been superseded by the TLS protocol
        ''' and is provided for backward compatibility only
        ''' </summary>
        ''' <remarks>
        ''' The AUTH SSL command is sent to the FTP server to secure the connection.  TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible.
        ''' There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same.
        ''' Some FTP server do not implement TLS or understand the command AUTH TLS.  In those situations you should specify the security
        ''' protocol Ssl3, otherwise specify Tls1.
        ''' </remarks>
        Ssl3Implicit
        ''' <summary>
        ''' Specifies Secure Socket Layer (SSL) version 2.0 is required to secure communications in implicit mode.  SSL 2.0 has been superseded by the TLS protocol
        ''' and is provided for backward compatibility only.  SSL 2.0 has several weaknesses and should only be used with legacy FTP server that require it.
        ''' </summary>
        ''' <remarks>
        ''' The AUTH SSL command is sent to the FTP server to secure the connection.  TLS protocol is the latest version of the SSL 3.0 protcol and is the security protocol that should be used whenever possible.
        ''' There are slight differences between SSL version 3.0 and TLS version 1.0, but the protocol remains substantially the same.
        ''' Some FTP server do not implement TLS or understand the command AUTH TLS.  In those situations you should specify the security
        ''' protocol Ssl3, otherwise specify Tls1.
        ''' </remarks>
        Ssl2Implicit
    End Enum

    ''' <summary>
    ''' IP network protocol options.
    ''' </summary>
    Public Enum NetworkVersion As Integer
        ''' <summary>
        ''' Internet Protocol version 4. 
        ''' </summary>
        ''' <remarks>
        ''' IPv4 is the most widely deployed IP addressing protocol used for Internet communications.  A typical IP v4 address uses dot notation such as 192.168.10.4.  The IPv4 protocol has 
        ''' limitations on addressing space as well as other issues and has been superceeded by the IPv6 protocol.  When choosing the IPv6 protocol for FTP communcations, the Starksoft
        ''' FtpClient will use the standard port and passive commands PORT/PASV.  Unless required, choose IPv4 for your IP network protocol addressing.
        ''' </remarks>
        IPv4
        ''' <summary>
        ''' Internet Protocol version 6.
        ''' </summary>
        ''' <remarks>
        ''' IPv6 is the newest Internet protcol version and is gaining adoption.  A typical IP v6 address uses colon notation such as 2001:0db8:85a3:0042:0000:8a2e:0370:7334.  The IPv4 protocol has 
        ''' limitations on addressing space as well as other issues and has been superceeded by the IPv6 protocol.  When choosing the IPv6 protocol for FTP communcations, the Starksoft
        ''' FtpClient will use the extended port and extended passive commands EPRT/EPSV which support IPv6 addressing.  Not all FTP servers support these extended commands.
        ''' Unless required, choose IPv4 for your IP network protocol addressing for highest compatability with existing FTP servers.
        ''' </remarks>
        IPv6
    End Enum

    ''' <summary>
    ''' The data transfer directory.
    ''' </summary>
    Friend Enum TransferDirection As Integer
        ''' <summary>
        ''' Transfer data from server to client.
        ''' </summary>
        ToClient
        ''' <summary>
        ''' Transfer data from client to server.
        ''' </summary>
        ToServer
    End Enum

#End Region

#Region "Public FTP Response Code Enum"

    ''' <summary>
    ''' Enumeration representing all the various response codes from a FTP server.
    ''' </summary>
    Public Enum FtpResponseCode As Integer
        ''' <summary>
        ''' No response was received from the server.
        ''' </summary>
        None = 0
        ''' <summary>
        ''' The command was executed sucessfully (200).
        ''' </summary>
        CommandOkay = 200
        ''' <summary>
        ''' A syntax error occurred because the command was not recognized (500).
        ''' </summary>
        SyntaxErrorCommandUnrecognized = 500
        ''' <summary>
        ''' A syntax error occurred because the input parameters or arguments for the command are invalid (501).
        ''' </summary>
        SyntaxErrorInParametersOrArguments = 501
        ''' <summary>
        ''' The command is considered superfluous and not implemented by the FTP server (202).
        ''' </summary>
        CommandNotImplementedSuperfluousAtThisSite = 202
        ''' <summary>
        ''' The command is not implement by the FTP server (502).
        ''' </summary>
        CommandNotImplemented = 502
        ''' <summary>
        ''' A bad sequence of commands was issued (503).
        ''' </summary>
        BadSequenceOfCommands = 503
        ''' <summary>
        ''' The command does not support the supplied parameter (504).
        ''' </summary>
        CommandNotImplementedForThatParameter = 504
        ''' <summary>
        ''' Restart marker reply (110).  MARK yyyy = mmmm  Where yyyy is User-process data 
        ''' stream marker, and mmmm server's equivalent marker (note the spaces between
        ''' markers and "=").
        ''' </summary>
        RestartMarkerReply = 110
        ''' <summary>
        ''' System status or system help reply (211).
        ''' </summary>
        SystemStatusOrHelpReply = 211
        ''' <summary>
        ''' Directory status (212).
        ''' </summary>
        DirectoryStatus = 212
        ''' <summary>
        ''' File status (213).
        ''' </summary>
        FileStatus = 213
        ''' <summary>
        ''' Help message (214).  On how to use the server or the meaning of a particular
        ''' non-standard command.  This reply is useful only to the human user.
        ''' </summary>
        HelpMessage = 214
        ''' <summary>
        ''' Name system type where Name is an official system name from the list in the
        ''' Assigned Numbers document (215).
        ''' </summary>
        NameSystemType = 215
        ''' <summary>
        ''' Service ready in xxx minutes (120).
        ''' </summary>
        ServiceReadyInxxxMinutes = 120
        ''' <summary>
        ''' Service is now ready for new user (220).
        ''' </summary>
        ServiceReadyForNewUser = 220
        ''' <summary>
        ''' Service is closing control connection (221).
        ''' </summary>
        ServiceClosingControlConnection = 221
        ''' <summary>
        ''' Service not available, closing control connection (421). This may be a reply to any 
        ''' command if the service knows it must shut down.
        ''' </summary>
        ServiceNotAvailableClosingControlConnection = 421
        ''' <summary>
        ''' Data connection already open; transfer starting (125).
        ''' </summary>
        DataConnectionAlreadyOpenSoTransferStarting = 125
        ''' <summary>
        ''' Data connection open so no transfer in progress (225).
        ''' </summary>
        DataConnectionOpenSoNoTransferInProgress = 225
        ''' <summary>
        ''' cannot open data connection (425).
        ''' </summary>
        CannotOpenDataConnection = 425
        ''' <summary>
        ''' Requested file action successful (for example, file transfer or file abort) (226).
        ''' </summary>
        ClosingDataConnection = 226
        ''' <summary>
        ''' Connection closed therefore the transfer was aborted (426).
        ''' </summary>
        ConnectionClosedSoTransferAborted = 426
        ''' <summary>
        ''' Entering Passive Mode (h1,h2,h3,h4,p1,p2) (227).
        ''' </summary>
        EnteringPassiveMode = 227
        ''' <summary>
        ''' User logged in, proceed (230).
        ''' </summary>
        UserLoggedIn = 230
        ''' <summary>
        ''' User is not logged in.  Command not accepted (530).
        ''' </summary>
        NotLoggedIn = 530
        ''' <summary>
        ''' The user name was accepted but the password must now be supplied (331).
        ''' </summary>
        UserNameOkayButNeedPassword = 331
        ''' <summary>
        ''' An account is needed for login (332).
        ''' </summary>
        NeedAccountForLogin = 332
        ''' <summary>
        ''' An account is needed for storing file on the server (532).
        ''' </summary>
        NeedAccountForStoringFiles = 532
        ''' <summary>
        ''' File status okay; about to open data connection (150).
        ''' </summary>
        FileStatusOkaySoAboutToOpenDataConnection = 150
        ''' <summary>
        ''' Requested file action okay, completed (250).
        ''' </summary>
        RequestedFileActionOkayAndCompleted = 250
        ''' <summary>
        ''' The pathname was created (257).
        ''' </summary>
        PathNameCreated = 257
        ''' <summary>
        ''' Requested file action pending further information (350).
        ''' </summary>
        RequestedFileActionPendingFurtherInformation = 350
        ''' <summary>
        ''' Requested file action not taken (450).  
        ''' </summary>
        RequestedFileActionNotTaken = 450
        ''' <summary>
        ''' Requested file action not taken (550).  File unavailable (e.g., file busy).
        ''' </summary>
        RequestedActionNotTakenFileUnavailable = 550
        ''' <summary>
        ''' Requested action aborted (451). Local error in processing.
        ''' </summary>
        RequestedActionAbortedDueToLocalErrorInProcessing = 451
        ''' <summary>
        ''' Requested action aborted (551). Page type unknown.
        ''' </summary>
        RequestedActionAbortedPageTypeUnknown = 551
        ''' <summary>
        ''' Requested action not taken (452).  Insufficient storage space in system.
        ''' </summary>
        RequestedActionNotTakenInsufficientStorage = 452
        ''' <summary>
        ''' Requested file action aborted (552).  Exceeded storage allocation (for current directory or dataset).
        ''' </summary>
        RequestedFileActionAbortedExceededStorageAllocation = 552
        ''' <summary>
        ''' Requested action not taken (553).  File name not allowed.
        ''' </summary>
        RequestedActionNotTakenFileNameNotAllowed = 553
        ''' <summary>
        ''' Secure authentication Okay (234).
        ''' </summary>
        AuthenticationCommandOkay = 234
        ''' <summary>
        ''' This reply indicates that the requested security mechanism
        ''' is ok, and includes security data to be used by the client
        ''' to construct the next command.  The square brackets are not
        ''' to be included in the reply, but indicate that
        ''' security data in the reply is optional.  (334)
        ''' </summary>
        ''' <remarks>
        ''' Example:
        '''      334 [ADAT=base64data]
        '''      ADAT base64data
        ''' 
        ''' See http://www.ietf.org/rfc/rfc2228.txt for more information.
        ''' </remarks>
        AuthenticationCommandOkaySecurityDataOptional = 334
        ''' <summary>
        ''' SSL service is Unavailable (431).
        ''' </summary>
        ServiceIsUnavailable = 431
    End Enum
#End Region

    ''' <summary>
    ''' Base abstract class for FtpClient.  Implements FTP network protocols.
    ''' </summary>
    Public MustInherit Class FtpBase
        Implements IDisposable

#Region "Constructors"

        ''' <summary>
        ''' Initializes a new instance of the FtpNetworkAdapter class.
        ''' </summary>
        ''' <param name="port">Port number the adapter is to communicate on.</param>
        ''' <param name="securityProtocol">Value indicating what secure security communications protocol should be used (if any).</param>
        Friend Sub New(port As Integer, securityProtocol As FtpSecurityProtocol)
            _port = port
            _securityProtocol = securityProtocol
        End Sub

        ''' <summary>
        ''' Initializes a new instance of the FtpNetworkAdapter class.
        ''' </summary>
        ''' <param name="host">Host the adapter is to communicate on.</param>
        ''' <param name="port">Port number the adapter is to communicate on.</param>
        ''' <param name="securityProtocol">Value indicating what secure security communications protocol should be used (if any).</param>
        Friend Sub New(host As String, port As Integer, securityProtocol As FtpSecurityProtocol)
            _host = host
            _port = port
            _securityProtocol = securityProtocol
        End Sub

#End Region

#Region "Private Variables and Constants"

        ' connection objects
        Private _networkProtocol As NetworkVersion = NetworkVersion.IPv4
        ' default is IPv4
        Private _commandConn As TcpClient
        Private _commandStream As Stream
        Private _dataConn As TcpClient

        ' connection settings and buffers
        Private _port As Integer
        Private _host As String
        Private _client As String
        ' optional client IP address used with Active mode connections
        Private _dataTransferMode As TransferMode = TransferMode.Passive
        ' default is passive mode
        Private _dataBuffer As Byte()
        Private _transferSize As Long = -1
        ' transfer size in bytes - default to -1 
        ' response queue objects
        Private _responseQueue As New FtpResponseQueue()
        Private _response As New FtpResponse()
        Private _responseList As New FtpResponseCollection()

        ' response monitor thread
        Private _responseMonitor As Thread
        Shared _reponseMonitorLock As New Object()

        ' proxy object 
        Private _proxy As IProxyClient

        ' features
        Private _feats As FtpFeatureCollection

        ' general TCP/IP settings
        Private _tcpBufferSize As Integer = TCP_BUFFER_SIZE
        Private _tcpTimeout As Integer = TCP_TIMEOUT
        Private _transferTimeout As Integer = TRANSFER_TIMEOUT
        Private _commandTimeout As Integer = COMMAND_TIMEOUT

        ' active data connection objects and settings
        Private _activeListener As TcpListener
        Private _activePort As Integer
        Private _activePortRangeMin As Integer = 50000
        Private _activePortRangeMax As Integer = 50080

        ' secure communications specific 
        Private _securityProtocol As FtpSecurityProtocol = FtpSecurityProtocol.None
        ' default is no encryption
        Private _serverCertificate As X509Certificate2
        Private _clientCertificates As New X509CertificateCollection()
        Private _alwaysAcceptServerCertificate As Boolean = False

        ' data compresion specific
        Private _isCompressionEnabled As Boolean = False
        ' default is no compression
        ' data hashing specific
        Private _autoHashAlgorithm As HashingAlgorithm = HashingAlgorithm.None
        ' default is no compression (zlib)
        ' character encoding object used by the client to send and receive data
        Private _encode As Encoding = Encoding.UTF8
        ' default always to UTF-8 which is standards compliant
        ' thread signal for active mode data transfer
        Private _activeSignal As New ManualResetEvent(False)

        ' async background worker event-based object
        Private _asyncWorker As BackgroundWorker
        Private _asyncCanceled As Boolean

        ' syncronization objects
        Private _cmdStreamWriteLock As Object = New [Object]()

        ' data throttle settings
        Private _maxUploadSpeed As Integer
        Private _maxDownloadSpeed As Integer

        'Private IP Passive Mode Fix
        Private _hostIpAddresses As IPAddress()

        ' constant definitions
        Private Const TCP_BUFFER_SIZE As Integer = 8192
        Private Const TCP_TIMEOUT As Integer = 30000
        ' 30 seconds
        Private Const WAIT_FOR_DATA_INTERVAL As Integer = 100
        ' 100 ms
        Private Const WAIT_FOR_COMMAND_RESPONSE_INTERVAL As Integer = 100
        ' 100 ms
        Private Const TRANSFER_TIMEOUT As Integer = 30000
        ' 30 seconds
        Private Const COMMAND_TIMEOUT As Integer = 30000
        ' 30 seconds
        Private Const HASH_CRC_32 As String = "CRC-32"
        Private Const HASH_MD5 As String = "MD5"
        Private Const HASH_SHA_1 As String = "SHA-1"
        Private Const HASH_SHA_256 As String = "SHA-256"
        Private Const HASH_SHA_512 As String = "SHA-512"


#End Region

#Region "Public Events"

        ''' <summary>
        ''' Server response event.
        ''' </summary>
        Public Event ServerResponse As EventHandler(Of FtpResponseEventArgs)

        ''' <summary>
        ''' Server request event.
        ''' </summary>
        Public Event ClientRequest As EventHandler(Of FtpRequestEventArgs)

        ''' <summary>
        ''' Data transfer progress event.
        ''' </summary>
        Public Event TransferProgress As EventHandler(Of TransferProgressEventArgs)

        ''' <summary>
        ''' Data transfer complete event.
        ''' </summary>
        Public Event TransferComplete As EventHandler(Of TransferCompleteEventArgs)

        ''' <summary>
        ''' Security certificate authentication event.
        ''' </summary>
        Public Event ValidateServerCertificate As EventHandler(Of ValidateServerCertificateEventArgs)

        ''' <summary>
        ''' Connection closed event.
        ''' </summary>
        Public Event ConnectionClosed As EventHandler(Of ConnectionClosedEventArgs)

#End Region

#Region "Public Methods"

        ''' <summary>
        ''' Cancels any asychronous operation that is currently active.
        ''' </summary>
        Public Sub CancelAsync()
            If _asyncWorker IsNot Nothing AndAlso Not _asyncWorker.CancellationPending AndAlso _asyncWorker.IsBusy Then
                _asyncCanceled = True
                _asyncWorker.CancelAsync()
            End If
        End Sub

        ''' <summary>
        ''' Gets the hash value from the FTP server for the file specified.  
        ''' Use this value to compare a local hash value to determine file integrity.
        ''' </summary>
        ''' <param name="algorithm">Hashing function to use.</param>
        ''' <param name="path">Path to the file ont the remote FTP server.</param>
        ''' <return>Hash value in a string format.</return>
        Public Function GetHash(algorithm As HashingAlgorithm, path As String) As String
            Return GetHash(algorithm, path, 0, 0)
        End Function

        ''' <summary>
        ''' Gets the hash value from the FTP server for the file specified.  
        ''' Use this value to compare a local hash to determine file integrity.
        ''' </summary>
        ''' <param name="algorithm">Hashing function to use.</param>
        ''' <param name="path">Path to the file on the remote FTP server.</param>
        ''' <param name="startPosition">Byte position of where the server should begin computing the hash.</param>
        ''' <param name="endPosition">Byte position of where the server should end computing the hash.</param>
        ''' <return>Hash value in a string format.</return>
        ''' <remarks>
        ''' FTP file hashing is not a fully ratified RFC standard and therefore is not widely supported.  There is
        ''' a convention that some FTP server support that allows for partial file hashing.  The command that allow
        ''' partial file hashing are XCRC, XMD5, XSHA1, XSHA256, XSHA512 as well as other variations not listed.
        ''' When computing a CRC, MD5 or SHA1 on a server that supports the commands, an optional startPosition
        ''' and endPosition value can also be specified.  These optional parameters allow for partial file hashing 
        ''' which can be used while resuming a file transfer. In that way only the additional bytes are hashed 
        ''' for accuracy and not the entire file on the FTP server.
        ''' 
        ''' The second FTP file hashing implementation is a RFC draft specification and uses a new FTP command named HASH.
        ''' The default hashing algorithm can be set with the OPTS HASH command specifying the supported algorithm as an argument.
        ''' Optionally, the HASH command supports partial file hashing with another command called RANG.  The RANG command
        ''' requires a byte range to use when calculating the file hash.  Partial file hashing with HASH is only supported if the 
        ''' command RANG is also a supported.  The HASH command also has several additional return error codes.  For example, 
        ''' an FTP server that implements the HASH command should reply with a 450 reply if the server is busy.  This signals
        ''' that the client can try again some time later.  In addition, an FTP server that implements the HASH command should reply with 
        ''' a 501 reply to the OPTS HASH command if the user has requested an unknown or unsupported algorithm.
        ''' 
        ''' The Starksoft FtpClient will attempt to determine which hashing command features are enabled on the FTP server 
        ''' and formulate the hashing request accordingly.  The default hashing command is always HASH.  If HASH is not available, the 
        ''' FtpClient will attempt to execute the appropriate alternative XCRC, XMD5, XSHA1, XSHA256 or XSHA512 command.  If the
        ''' alternate command is not supported then a FileHashingException will be thrown.
        ''' 
        ''' For partial file hashing, if HASH is supported but RANG is not, the FtpClient will attempted to execute the approriate XCRC, XMD5, XSHA1, 
        ''' XSHA256 or XSHA512 command instead.  If the FTP server does not support the alternate hashing command then an exception will be thrown.
        ''' 
        ''' See RFC draft-ietf-ftpext2-hash-03 for more information            
        ''' http://tools.ietf.org/html/draft-ietf-ftpext2-hash-03#section-3.2
        ''' </remarks>
        Public Function GetHash(algorithm As HashingAlgorithm, path As String, startPosition As Long, endPosition As Long) As String
            If algorithm = HashingAlgorithm.None Then
                Throw New ArgumentOutOfRangeException("algorithm", "must contain a value other than 'Unknown'")
            End If
            If startPosition < 0 Then
                Throw New ArgumentOutOfRangeException("startPosition", "must contain a value greater than or equal to 0")
            End If
            If endPosition < 0 Then
                Throw New ArgumentOutOfRangeException("startPosition", "must contain a value greater than or equal to 0")
            End If
            If startPosition > endPosition Then
                Throw New ArgumentOutOfRangeException("startPosition", "must contain a value less than or equal to endPosition")
            End If

            ' gather information about the hashing methods supported on the FTP server
            Dim hashArgText As String = ConvertHashAlgoToTextArg(algorithm)
            Dim hashXCmd As FtpCmd = ConvertHashXAlgoToTextCmd(algorithm)
            Dim hf As FtpFeature = Features.Find(FtpCmd.Hash)
            Dim hfa As FtpFeatureArgument = Nothing
            If hf IsNot Nothing Then
                hfa = hf.Arguments.Find(hashArgText)
            End If
            Dim rf As FtpFeature = Features.Find(FtpCmd.Rang)

            Dim partialReq As Boolean = If(startPosition > 0, True, False)
            Dim canHash As Boolean = If(hf IsNot Nothing AndAlso hfa IsNot Nothing, True, False)
            Dim canHashPartial As Boolean = If(rf IsNot Nothing, True, False)


            If partialReq AndAlso (Not canHash OrElse Not canHashPartial) OrElse (Not partialReq AndAlso Not canHash) Then
                Try
                    If partialReq Then
                        SendRequest(New FtpRequest(_encode, hashXCmd, path, startPosition.ToString(), endPosition.ToString()))
                    Else
                        SendRequest(New FtpRequest(_encode, ConvertHashXAlgoToTextCmd(algorithm), path))
                    End If
                Catch x As FtpException
                    Throw New FtpHashingException([String].Format("Unable to partial hash file using {0} command.", hashXCmd.ToString().ToUpper()), x)
                End Try
            Else
                Try
                    ' set the hash function if it is not already set
                    If Not hfa.IsDefault Then
                        SetHashOption(algorithm)
                    End If

                    If partialReq Then
                        ' send RANGe command if it is a partial hash
                        SendRequest(New FtpRequest(_encode, FtpCmd.Rang, startPosition.ToString(), endPosition.ToString()))
                    End If

                    ' send the HASH command 
                    SendRequest(New FtpRequest(_encode, FtpCmd.Hash, path))
                Catch ex2 As FtpException
                    If _response.Code = FtpResponseCode.SyntaxErrorInParametersOrArguments Then
                        Throw New FtpHashingInvalidAlgorithmException("FTP server does not support selected hashing algorithm.")
                    ElseIf _response.Code = FtpResponseCode.RequestedFileActionNotTaken Then
                        Throw New FtpHashingServerBusyException("FTP server is busy.  Please try again later.")
                    Else
                        Throw New FtpHashingException("Unable to partial hash file using HASH command.", ex2)
                    End If

                End Try
            End If

            Return _response.Text
        End Function

        ''' <summary>
        ''' Sets the hashing algorithm option on the remote FTP server.
        ''' </summary>
        ''' <remarks>
        ''' Not all FTP servers support the HASH feature.
        ''' </remarks>
        ''' <param name="algorithm">Hasing algorithm to use</param>
        ''' <exception cref="ArgumentOutOfRangeException"></exception>
        ''' <exception cref="FtpCommandNotSupportedException"></exception>
        ''' <exception cref="FtpHashingException"></exception>
        Public Sub SetHashOption(algorithm As HashingAlgorithm)
            If algorithm = HashingAlgorithm.None Then
                Throw New ArgumentOutOfRangeException("algorithm")
            End If
            If Not _feats.Contains(FtpCmd.Hash) Then
                Throw New FtpCommandNotSupportedException("Cannot set the HASH option on FTP server.", FtpCmd.Hash)
            End If

            ' get the hash argument feature from the features collection
            Dim fa As FtpFeatureArgument = GetHashFeatureArgument(algorithm)

            Dim hashArgText As String = ConvertHashAlgoToTextArg(algorithm)

            ' attempt to send a request to change the option for the HASH function
            Dim hashCmd As String = [String].Format("HASH {0}", hashArgText)
            Try
                SendRequest(New FtpRequest(_encode, FtpCmd.Opts, hashCmd))
            Catch ex As FtpException
                Throw New FtpHashingException("Unable to set hashing option.", ex)
            End Try

            ' refresh the features collection
            TryResetFeatures()
            ' find the Hash feature argument agains and verify the option has been set properly
            Dim fa2 As FtpFeatureArgument = GetHashFeatureArgument(algorithm)
            If Not fa2.IsDefault Then
                Throw New FtpHashingException("Hashing option not set to default by FTP server.")
            End If
        End Sub

        ''' <summary>
        ''' Computes a cryptographic hash or CRC value for a local file.
        ''' </summary>
        ''' <param name="hash">Hashing function to use.</param>
        ''' <param name="localPath">Path to file to perform hashing operation on.</param>
        ''' <return>Hash value in a string format.</return>
        Public Function ComputeHash(hash As HashingAlgorithm, localPath As String) As String
            If Not File.Exists(localPath) Then
                Throw New ArgumentException("file does not exist.", "localPath")
            End If

            Using fileStream As FileStream = File.OpenRead(localPath)
                Return ComputeHash(hash, fileStream)
            End Using
        End Function

        ''' <summary>
        ''' Computes a hash value for a Stream object.
        ''' </summary>
        ''' <param name="hash">Hashing function to use.</param>
        ''' <param name="inputStream">Any System.IO.Stream object.</param>
        ''' <return>Hash value in a string format.</return>
        ''' <remarks>
        ''' The Stream object must allow reads and must allow seeking.
        ''' </remarks>
        Public Function ComputeHash(hash As HashingAlgorithm, inputStream As Stream) As String
            Return ComputeHash(hash, inputStream, 0)
        End Function

        ''' <summary>
        ''' Computes a hash value for a Stream object at a specific start position.
        ''' </summary>
        ''' <param name="hash">Hashing function to use.</param>
        ''' <param name="inputStream">Any System.IO.Stream object.</param>
        ''' <param name="startPosition">Byte position of where the hash computation should begin.</param>
        ''' <return>Hash value in a string format.</return>
        ''' <remarks>
        ''' The Stream object must allow reads and must allow seeking.
        ''' </remarks>
        Public Shared Function ComputeHash(hash As HashingAlgorithm, inputStream As Stream, startPosition As Long) As String
            If inputStream Is Nothing Then
                Throw New ArgumentNullException("inputStream")
            End If
            If Not inputStream.CanRead Then
                Throw New ArgumentException("must be readable.  The CanRead property must return a value of 'true'.", "inputStream")
            End If
            If Not inputStream.CanSeek Then
                Throw New ArgumentException("must be seekable.  The CanSeek property must return a value of 'true'.", "inputStream")
            End If
            If startPosition < 0 Then
                Throw New ArgumentOutOfRangeException("startPosition", "must contain a value greater than or equal to 0")
            End If

            Dim hashAlgo As HashAlgorithm = Nothing

            Select Case hash
                'Case HashingAlgorithm.Crc32
                '    hashAlgo = New Starksoft.Cryptography.Crc32()
                'Exit Select
                Case HashingAlgorithm.Md5
                    hashAlgo = New MD5CryptoServiceProvider()
                    Exit Select
                Case HashingAlgorithm.Sha1
                    hashAlgo = New SHA1CryptoServiceProvider()
                    Exit Select
                    'Case HashingAlgorithm.Sha256
                    '    hashAlgo = New SHA256CryptoServiceProvider()
                    '    Exit Select
                    'Case HashingAlgorithm.Sha512
                    '    hashAlgo = New SHA512CryptoServiceProvider()
                    '    Exit Select
            End Select

            If startPosition > 0 Then
                inputStream.Position = startPosition
            Else
                inputStream.Position = 0
            End If

            Dim hashArray As Byte() = hashAlgo.ComputeHash(inputStream)

            ' convert byte array to a string
            Dim buffer As New StringBuilder(hashArray.Length)
            For Each hashByte As Byte In hashArray
                buffer.Append(hashByte.ToString("x2"))
            Next

            Return buffer.ToString()
        End Function

        ''' <summary>
        ''' Get the additional features supported by the remote FTP server as a text string.  
        ''' </summary>
        ''' <return>A string containing the additional features beyond the RFC 959 standard supported by the FTP server.</return>
        ''' <remarks>
        ''' This command is an additional feature beyond the RFC 959 standard and therefore is not supported by all FTP servers.        
        ''' </remarks>
        ''' <exception cref="FtpFeatureException"></exception>
        Public Function GetFeatures() As String
            Try
                SendRequest(New FtpRequest(Encoding, FtpCmd.Feat))
            Catch fex As FtpException
                Throw New FtpFeatureException("An error occurred when retrieving features.", LastResponse, fex)
            End Try

            Return LastResponseList.GetRawText()
        End Function

#End Region

#Region "Internal Properties"

        Friend ReadOnly Property AsyncWorker() As BackgroundWorker
            Get
                Return _asyncWorker
            End Get
        End Property

        ''' <summary>
        ''' Get a reference to the internal features list.
        ''' </summary>
        Friend ReadOnly Property Features() As FtpFeatureCollection
            Get
                Return _feats
            End Get
        End Property

#End Region

#Region "Public Properties"

        ''' <summary>
        ''' Gets a value indicating whether an asynchronous operation is canceled.
        ''' </summary>
        ''' <remarks>return true if an asynchronous operation is canceled; otherwise, false.
        ''' </remarks>
        Public ReadOnly Property IsAsyncCanceled() As Boolean
            Get
                Return _asyncCanceled
            End Get
        End Property

        ''' <summary>
        ''' Gets a value indicating whether an asynchronous operation is running.
        ''' </summary>
        ''' <remarks>return true if an asynchronous operation is running; otherwise, false.
        ''' </remarks>
        Public ReadOnly Property IsBusy() As Boolean
            Get
                Return If(_asyncWorker Is Nothing, False, _asyncWorker.IsBusy)
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the current port number used by the FtpClient to make a connection to the FTP server.
        ''' </summary>
        ''' <remarks>
        ''' The default value is '80'.  This setting can only be changed when the 
        ''' connection to the FTP server is closed.  And FtpException is thrown if this 
        ''' setting is changed when the FTP server connection is open.
        ''' 
        ''' return an integer representing the port number used to connect to a remote server.
        ''' </remarks>
        Public Property Port() As Integer
            Get
                Return _port
            End Get
            Set(value As Integer)
                If Me.IsConnected Then
                    Throw New FtpException("Port property value cannot be changed when connection is open.")
                End If
                _port = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets a text value containing the current host used by the FtpClient to make a connection to the FTP server.
        ''' </summary>
        ''' <remarks>
        ''' This value may be in the form of either a host name or IP address.
        ''' This setting can only be changed when the 
        ''' connection to the FTP server is closed.  
        ''' 
        ''' return a string with either the host name or host ip address.
        ''' </remarks>
        Public Property Host() As String
            Get
                Return _host
            End Get
            Set(value As String)
                If Me.IsConnected Then
                    Throw New FtpException("Host property value cannot be changed when connection is open.")
                End If
                _host = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets a text value containing the IPv4 or IPv6 client IP address used by the FtpClient to make active connections to the FTP server.
        ''' </summary>
        ''' <remarks>
        ''' The NetworkProtocol property must be properly specified and must match the type of client IP address specified.  For example, if an IPv6
        ''' address is specified in the Client property, the NetworkProtocol must be set to IPv6.
        ''' 
        ''' The client IP address is not required and can be optionally provided as a IP address for the client to use when transmitted
        ''' Active mode PORT command information to the FTP server.  If this value is empty the Starksoft FtpClient will choose an 
        ''' available IP address on the client machine.  In some cases it is desirable to provide a specific IP address of the external border router
        ''' to an internal network.  Not all firewall devices will automatically switch the IP address for the PORT or EPRT commands.  In those cases
        ''' the external facing IP address can be provided for use.  In addition, some clients may find that a specific IP address on the client
        ''' local host machine should be used for making Active mode connections.  Note the IP address provided is not verified as a valid local
        ''' host IP address.
        ''' 
        ''' This value may be in the form of either a host name or IP address.
        ''' This setting can only be changed when the 
        ''' connection to the FTP server is closed.  
        ''' return a string with either the host name or host ip address.
        ''' </remarks>
        ''' <seealso cref="NetworkProtocol"/>
        ''' <seealso cref="Host"/>
        ''' <seealso cref="Port"/>
        Public Property Client() As String
            Get
                Return _client
            End Get
            Set(value As String)
                If Me.IsConnected Then
                    Throw New FtpException("Client property value cannot be changed when connection is open.")
                End If

                Try
                    IPAddress.Parse(value)
                Catch generatedExceptionName As FormatException
                    Throw New ArgumentException("The Client IP address must be a valid IPv4 or IPv6 notated Internet Protocol address.")
                End Try

                _client = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets a value indicating what security protocol such as Secure Sock Layer (SSL) should be used.
        ''' </summary>
        ''' <remarks>
        ''' The default value is 'None'.  This setting can only be changed when the 
        ''' connection to the FTP server is closed.  An FtpException is thrown if this 
        ''' setting is changed when the FTP server connection is open.
        ''' </remarks>
        ''' <seealso cref="SecurityCertificates"/>
        ''' <seealso cref="ValidateServerCertificate" />
        Public Property SecurityProtocol() As FtpSecurityProtocol
            Get
                Return _securityProtocol
            End Get
            Set(value As FtpSecurityProtocol)
                If Me.IsConnected Then
                    Throw New FtpException("SecurityProtocol property value cannot be changed when connection is open.")
                End If
                _securityProtocol = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets a value indicating what network protocol should be used..
        ''' </summary>
        ''' <remarks>
        ''' The default value is 'Ipv4'.  This setting can only be changed when the 
        ''' connection to the FTP server is closed.  An FtpException is thrown if this 
        ''' setting is changed when the FTP server connection is open.
        ''' </remarks>
        ''' <seealso cref="Client"/>
        ''' <seealso cref="Host"/>
        ''' <seealso cref="Port"/>
        Public Property NetworkProtocol() As NetworkVersion
            Get
                Return _networkProtocol
            End Get
            Set(value As NetworkVersion)
                If Me.IsConnected Then
                    Throw New FtpException("NetworkProtocol property value cannot be changed when connection is open.")
                End If
                _networkProtocol = value
            End Set
        End Property

        ''' <summary>
        ''' Get Client certificate collection used when connection with a secured SSL/TSL protocol.  Add your client certificates 
        ''' if required to connect to the remote FTP server.
        ''' </summary>
        ''' <remarks>return a X509CertificateCollection list contains X.509 security certificates.</remarks>
        ''' <seealso cref="SecurityProtocol"/>
        ''' <seealso cref="ValidateServerCertificate" />
        Public ReadOnly Property SecurityCertificates() As X509CertificateCollection
            Get
                Return _clientCertificates
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets a value indicating that the client will use compression when uploading and downloading
        ''' data.
        ''' </summary>
        ''' <remarks>
        ''' This value turns on or off the compression algorithm DEFLATE to facility FTP data compression which is compatible with
        ''' FTP servers that implement compression via the zLib compression software library.  The default value is 'False'.  
        ''' This setting can only be changed when the system is not busy conducting other operations.  
        ''' 
        ''' return True if compression is enabled; otherwise False;
        ''' </remarks>
        Public Property IsCompressionEnabled() As Boolean
            Get
                Return _isCompressionEnabled
            End Get
            Set(value As Boolean)
                If Me.IsBusy Then
                    Throw New FtpBusyException("IsCompressionEnabled")
                End If
                Try
                    ' enable compression
                    If Me.IsConnected AndAlso value AndAlso value <> _isCompressionEnabled Then
                        CompressionOn()
                    End If

                    ' disable compression
                    If Me.IsConnected AndAlso Not value AndAlso value <> _isCompressionEnabled Then
                        CompressionOff()
                    End If
                Catch ex As FtpException
                    Throw New FtpDataCompressionException("An error occurred while trying to enable or disable FTP data compression.", ex)
                End Try
                _isCompressionEnabled = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets an Integer value representing the maximum upload speed allowed 
        ''' for data transfers in kilobytes per second.
        ''' </summary>
        ''' <remarks>
        ''' Set this value when you would like to throttle back any upload data transfers.
        ''' A value of zero means there is no restriction on how fast data uploads are 
        ''' conducted.  The default value is zero.  This setting is used to throttle data traffic so the FtpClient does
        ''' not consume all available network bandwidth.
        ''' </remarks>
        ''' <seealso cref="MaxDownloadSpeed"/>
        Public Property MaxUploadSpeed() As Integer
            Get
                Return _maxUploadSpeed
            End Get
            Set(value As Integer)
                If Me.IsBusy Then
                    Throw New FtpBusyException("MaxUploadSpeed")
                End If
                If value * 1024 > Int32.MaxValue OrElse value < 0 Then
                    Throw New ArgumentOutOfRangeException("value", "The MaxUploadSpeed property must have a range of 0 to 2,097,152.")
                End If
                _maxUploadSpeed = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets an Integer value representing the maximum download speed allowed 
        ''' for data transfers in kilobytes per second.
        ''' </summary>
        ''' <remarks>
        ''' Set this value when you would like to throttle back any download data transfers.
        ''' A value of zero means there is no restriction on how fast data uploads are 
        ''' conducted.  The default value is zero.  This setting is used to throttle data traffic so the FtpClient does
        ''' not consume all available network bandwidth.
        ''' </remarks>
        ''' <seealso cref="MaxUploadSpeed"/>
        Public Property MaxDownloadSpeed() As Integer
            Get
                Return _maxDownloadSpeed
            End Get
            Set(value As Integer)
                If Me.IsBusy Then
                    Throw New FtpBusyException("MaxDownloadSpeed")
                End If
                If value * 1024 > Int32.MaxValue OrElse value < 0 Then
                    Throw New ArgumentOutOfRangeException("value", "must have a range of 0 to 2,097,152.")
                End If
                _maxDownloadSpeed = value
            End Set
        End Property

        ''' <summary>
        ''' Gets only the last response from the FTP server.
        ''' </summary>
        ''' <remarks>return a FtpResponse object containing the last FTP server response; other the value null (or Nothing in VB) is returned.</remarks>
        Public ReadOnly Property LastResponse() As FtpResponse
            Get
                Return _response
            End Get
        End Property

        ''' <summary>
        ''' Gets the list of all responses since the last command was issues to the server.
        ''' </summary>
        ''' <remarks>return a FtpResponseCollection list containing all the responses.</remarks>
        Public ReadOnly Property LastResponseList() As FtpResponseCollection
            Get
                Return _responseList
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the TCP buffer size used when communicating with the FTP server in bytes.
        ''' </summary>
        ''' <remarks>return an integer value representing the buffer size.  The default value is 8192.</remarks>
        Public Property TcpBufferSize() As Integer
            Get
                Return _tcpBufferSize
            End Get
            Set(value As Integer)
                If Me.IsConnected Then
                    Throw New FtpException("TcpBufferSize property value cannot be changed when connection is open.")
                End If
                If value < 1 Then
                    Throw New ArgumentOutOfRangeException("value", "must be greater than 0.")
                End If
                _tcpBufferSize = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the TCP timeout used when communciating with the FTP server in milliseconds.
        ''' </summary>
        ''' <remarks>
        ''' Default value is 30000 (30 seconds).
        ''' </remarks>
        ''' <seealso cref="TransferTimeout"/>
        ''' <seealso cref="CommandTimeout"/>
        Public Property TcpTimeout() As Integer
            Get
                Return _tcpTimeout
            End Get
            Set(value As Integer)
                If Me.IsConnected Then
                    Throw New FtpException("TcpTimeout property value cannot be changed when connection is open.")
                End If
                If value < 0 Then
                    Throw New ArgumentOutOfRangeException("value", "must be greater than or equal to 0.")
                End If
                _tcpTimeout = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the data transfer timeout used when communicating with the FTP server in milliseconds.
        ''' </summary>
        ''' <remarks>
        ''' Default value is 30000 (30 seconds).
        ''' </remarks>
        ''' <seealso cref="TcpTimeout"/>
        ''' <seealso cref="CommandTimeout"/>
        Public Property TransferTimeout() As Integer
            Get
                Return _transferTimeout
            End Get
            Set(value As Integer)
                If Me.IsBusy Then
                    Throw New FtpBusyException("TransferTimeout")
                End If
                If value < 1 Then
                    Throw New ArgumentOutOfRangeException("value", "must be greater than 0.")
                End If
                _transferTimeout = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the FTP command timeout used when communciating with the FTP server in milliseconds.
        ''' </summary>
        ''' <remarks>
        ''' Default value is 30000 (30 seconds).
        ''' </remarks>
        ''' <seealso cref="TcpTimeout"/>
        ''' <seealso cref="TransferTimeout"/>
        Public Property CommandTimeout() As Integer
            Get
                Return _commandTimeout
            End Get
            Set(value As Integer)
                If Me.IsConnected Then
                    Throw New FtpException("CommandTimeout property value cannot be changed when connection is open.")
                End If
                If value < 1 Then
                    Throw New ArgumentOutOfRangeException("value", "must be greater than 0.")
                End If
                _commandTimeout = value
            End Set
        End Property

        ''' <summary>
        ''' The beginning port number range used by the FtpClient when opening a local 'Active' port.  The default value is 4051.
        ''' </summary>
        ''' <exception cref="ArgumentOutOfRangeException">Value must be less than or equal to the ActivePortRangeMax value.</exception>
        ''' <remarks>
        ''' When the FtpClient is in 'Active' mode a local port is opened for communications from the FTP server.
        ''' The FtpClient will attempt to open an unused TCP listener port between the ActivePortRangeMin and ActivePortRangeMax values.
        ''' Default value is 50000.
        ''' </remarks>
        ''' <seealso cref="ActivePortRangeMax"/>
        ''' <seealso cref="DataTransferMode"/>
        Public Property ActivePortRangeMin() As Integer
            Get
                Return _activePortRangeMin
            End Get
            Set(value As Integer)
                If Me.IsBusy Then
                    Throw New FtpBusyException("ActivePortRangeMin")
                End If
                If value > _activePortRangeMin Then
                    Throw New ArgumentOutOfRangeException("value", "must be less than the ActivePortRangeMax value.")
                End If
                If value < 1 OrElse value > 65534 Then
                    Throw New ArgumentOutOfRangeException("value", "must be between 1 and 65534.")
                End If
                _activePortRangeMin = value
            End Set
        End Property

        ''' <summary>
        ''' The ending port number range used by the FtpClient when opening a local 'Active' port.  The default value is 4080.
        ''' </summary>
        ''' <exception cref="ArgumentOutOfRangeException">Value must be greater than or equal to the ActivePortRangeMin value.</exception>
        ''' <remarks>
        ''' When the FtpClient is in 'Active' mode a local port is opened for communications from the FTP server.
        ''' The FtpClient will attempt to open an unused TCP listener port between the ActivePortRangeMin and ActivePortRangeMax values.
        ''' Default value is 50080.
        ''' </remarks>
        ''' <seealso cref="ActivePortRangeMin"/>
        ''' <seealso cref="DataTransferMode"/>
        Public Property ActivePortRangeMax() As Integer
            Get
                Return _activePortRangeMax
            End Get
            Set(value As Integer)
                If Me.IsBusy Then
                    Throw New FtpBusyException("ActivePortRangeMax")
                End If
                If value < _activePortRangeMin Then
                    Throw New ArgumentOutOfRangeException("value", "must be greater than the ActivePortRangeMin value.")
                End If
                If value < 1 OrElse value > 65534 Then
                    Throw New ArgumentOutOfRangeException("value", "must be between 1 and 65534.")
                End If
                _activePortRangeMax = value
            End Set
        End Property


        ''' <summary>
        ''' Gets or sets the data transfer mode to either Active or Passive.
        ''' </summary>
        ''' <seealso cref="ActivePortRangeMin"/>
        ''' <seealso cref="ActivePortRangeMax"/>
        Public Property DataTransferMode() As TransferMode
            Get
                Return _dataTransferMode
            End Get
            Set(value As TransferMode)
                If Me.IsBusy Then
                    Throw New FtpBusyException("DataTransferMode")
                End If
                _dataTransferMode = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the the proxy object to use when establishing a connection to the remote FTP server.
        ''' </summary>
        ''' <remarks>Create a proxy object when traversing a firewall.</remarks>
        ''' <code>
        ''' FtpClient ftp = new FtpClient();
        '''
        ''' // create an instance of the client proxy factory for the an ftp client
        ''' ftp.Proxy = (new ProxyClientFactory()).CreateProxyClient(ProxyType.Http, "localhost", 6588);
        '''        
        ''' </code>
        Public Property Proxy() As IProxyClient
            Get
                Return _proxy
            End Get
            Set(value As IProxyClient)
                If Me.IsConnected Then
                    Throw New FtpException("Proxy property value cannot be changed when connection is open.")
                End If
                _proxy = value
            End Set
        End Property

        ''' <summary>
        ''' Gets the connection status to the FTP server.
        ''' </summary>
        ''' <remarks>return True if the connection is open; otherwise False.</remarks>
        ''' <seealso cref="ConnectionClosed"/>
        Public ReadOnly Property IsConnected() As Boolean
            Get
                If _commandConn Is Nothing OrElse _commandConn.Client Is Nothing Then
                    Return False
                End If

                Dim client As Socket = _commandConn.Client
                If Not client.Connected Then
                    Return False
                End If

                Dim connected As Boolean = True
                Try
                    Dim tmp As Byte() = New Byte(0) {}

                    ' sychronize on streamWriteLock object to prevent non-blocking exception errors
                    SyncLock _cmdStreamWriteLock
                        ' attempt to write no data in hopes of getting status on the socket
                        client.Send(tmp, 0, 0)
                    End SyncLock
                Catch e As SocketException
                    ' 10035 == WSAEWOULDBLOCK
                    If Not e.NativeErrorCode.Equals(10035) Then
                        connected = False
                    End If
                Catch generatedExceptionName As ObjectDisposedException
                End Try

                Return connected
            End Get
        End Property


        ''' <summary>
        ''' Sets the automatic file integrity setting (cryptographic hash or CRC value) option on all data transfers (upload and download).
        ''' </summary>
        ''' <remarks>
        ''' The FtpClient library will throw an FtpFileIntegrityException if the file integrity value do not match.
        ''' 
        ''' Not all FTP servers support file integrity values such as SHA1, CRC32, or MD5.  If you server does support
        ''' one of these file integrity options, you can set this property and the FtpClient will automatically check
        ''' each file that is transferred to make sure the hash values match.  If the values do not match, an exception
        ''' is thrown.
        ''' </remarks>
        ''' <seealso cref="FtpHashingException"/>
        Public Property AutomaticFileHashing() As HashingAlgorithm
            Get
                Return _autoHashAlgorithm
            End Get
            Set(value As HashingAlgorithm)
                If Me.IsBusy Then
                    Throw New FtpBusyException("AutomaticFileHashing")
                End If
                _autoHashAlgorithm = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the boolean value indicating if the server certificate should
        ''' always be accepted for TLS/SSL secure connections.
        ''' </summary>
        ''' <remarks>
        ''' If this value is set to true, the ValidateServerCertificate event will not fire and all
        ''' TLS/SSL connections will not ask the client to validate the integrity of the server certificate.
        ''' </remarks>
        Public Property AlwaysAcceptServerCertificate() As Boolean
            Get
                Return _alwaysAcceptServerCertificate
            End Get
            Set(value As Boolean)
                If Me.IsConnected Then
                    Throw New FtpException("AlwaysAcceptServerCertificate property value cannot be changed when the connection is open.")
                End If
                _alwaysAcceptServerCertificate = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the internal character encoding object use to encode the request and response data.
        ''' </summary>
        Friend Property Encoding() As System.Text.Encoding
            Get
                Return _encode
            End Get
            Set(value As System.Text.Encoding)
                _encode = value
            End Set
        End Property

#End Region

#Region "Internal Protected Methods"

        Friend Sub TryResetFeatures()
            _feats = Nothing
            TrySetFeatures()
        End Sub

        Friend Sub TrySetFeatures()
            ' if the features collection has not be set then create it
            If _feats Is Nothing Then
                Dim s As String = ""
                Try
                    s = GetFeatures()
                Catch generatedExceptionName As FtpException
                    _feats = New FtpFeatureCollection()
                End Try
                If _feats Is Nothing Then
                    _feats = New FtpFeatureCollection(s)
                End If
            End If
        End Sub

        ''' <summary>
        ''' Send a FTP command request to the server.
        ''' </summary>
        ''' <param name="request"></param>
        Friend Sub SendRequest(request As FtpRequest)

            If _commandConn Is Nothing OrElse _commandConn.Connected = False Then
                Throw New FtpConnectionClosedException("Connection is closed.")
            End If

            ' clear out any responses that might have been pending from a previous
            ' failed operation
            DontWaitForHappyCodes()

            RaiseEvent ClientRequest(Me, New FtpRequestEventArgs(request))

            Dim buffer As Byte() = request.GetBytes()

            ' write request to debug out
            Debug.Write("S: " & _encode.GetString(buffer))

            Try
                ' sychronize on streamWriteLock object to prevent non-blocking exception errors
                SyncLock _cmdStreamWriteLock
                    _commandStream.Write(buffer, 0, buffer.Length)
                End SyncLock
            Catch ex As IOException
                Throw New FtpConnectionBrokenException("Connection is broken.  Failed to send command.", ex)
            End Try

            ' most commands will have happy codes but the quote() command does not 
            If request.HasHappyCodes Then
                WaitForHappyCodes(request.GetHappyCodes())
            Else
                ' when there are no happy codes given the we have to give the server some time to response
                ' since we really don't know what response is the correct one
                If request.Command <> FtpCmd.Quit Then
                    Thread.Sleep(2000)
                End If
                DontWaitForHappyCodes()
            End If
        End Sub

        ''' <summary>
        ''' creates a new async worker object for the async events to use.
        ''' </summary>
        Friend Sub CreateAsyncWorker()
            If _asyncWorker IsNot Nothing Then
                _asyncWorker.Dispose()
            End If
            _asyncWorker = Nothing
            _asyncCanceled = False
            _asyncWorker = New BackgroundWorker()
        End Sub

        ''' <summary>
        ''' Closes all connections to the FTP server.
        ''' </summary>
        Friend Sub CloseAllConnections()
            CloseDataConn()
            CloseCommandConn()
            AbortMonitorThread()
        End Sub

        ''' <summary>
        ''' The monitor thread should close automatically once the command connection is terminated.  If it does not close properly, force it to close.
        ''' </summary>
        Private Sub AbortMonitorThread()
            If _responseMonitor IsNot Nothing Then
                _responseMonitor.Abort()
            End If
        End Sub

        '  open a connection to the server
        Friend Sub OpenCommandConn()
            '  create a new tcp client object 
            CreateCommandConn()

            StartCommandMonitorThread()

            If _securityProtocol = FtpSecurityProtocol.Ssl2Explicit OrElse _securityProtocol = FtpSecurityProtocol.Ssl3Explicit OrElse _securityProtocol = FtpSecurityProtocol.Tls1Explicit OrElse _securityProtocol = FtpSecurityProtocol.Tls1OrSsl3Explicit Then
                CreateSslExplicitCommandStream()
            End If

            If _securityProtocol = FtpSecurityProtocol.Ssl2Implicit OrElse _securityProtocol = FtpSecurityProtocol.Ssl3Implicit OrElse _securityProtocol = FtpSecurityProtocol.Tls1Implicit OrElse _securityProtocol = FtpSecurityProtocol.Tls1OrSsl3Implicit Then
                CreateSslImplicitCommandStream()
            End If

            ' test to see if this is an asychronous operation and if so make sure 
            ' the user has not requested the operation to be canceled
            If IsAsyncCancellationPending() Then
                Return
            End If

            ' this check screws up secure connections so we have to ignore it when secure connections are enabled
            If _securityProtocol = FtpSecurityProtocol.None Then
                WaitForHappyCodes(FtpResponseCode.ServiceReadyForNewUser)
            End If

        End Sub

        Friend Sub TransferData(direction As TransferDirection, request As FtpRequest, data As Stream)
            TransferData(direction, request, data, 0)
        End Sub

        Friend Sub TransferData(direction As TransferDirection, request As FtpRequest, data As Stream, restartPosition As Long)
            If _commandConn Is Nothing OrElse _commandConn.Connected = False Then
                Throw New FtpConnectionClosedException("Connection is closed.")
            End If

            If request Is Nothing Then
                Throw New ArgumentNullException("request", "value is required")
            End If

            If data Is Nothing Then
                Throw New ArgumentNullException("data", "value is required")
            End If

            Select Case direction
                Case TransferDirection.ToClient
                    If Not data.CanWrite Then
                        Throw New FtpDataTransferException("Data transfer error.  Data conn does not allow write operation.")
                    End If
                    Exit Select
                Case TransferDirection.ToServer
                    If Not data.CanRead Then
                        Throw New FtpDataTransferException("Data transfer error.  Data conn does not allow read operation.")
                    End If
                    Exit Select
            End Select

            ' if this is a restart then the data stream must support seeking
            If restartPosition > 0 AndAlso Not data.CanSeek Then
                Throw New FtpDataTransferException("Data transfer restart error.  Data conn does not allow seek operation.")
            End If

            OpenDataConnAndTransferData(direction, request, data, restartPosition)
        End Sub


        Friend Sub OpenDataConnAndTransferData(direction As TransferDirection, request As FtpRequest, data As Stream, restartPosition As Long)
            Try
                ' create a thread to begin the porocess of opening a data connection to the remote server
                OpenDataConn()

                '  check for a restart position 
                If restartPosition > 0 Then
                    ' instruct the server to restart file transfer at the same position where the output stream left off
                    SendRequest(New FtpRequest(_encode, FtpCmd.Rest, restartPosition.ToString(CultureInfo.InvariantCulture)))

                    ' set the data stream to the same position as the server
                    data.Position = restartPosition
                End If

                ' send the data transfer command that requires a separate data connection to be established to transmit data
                SendRequest(request)

                ' wait for the data connection thread to signal back that a connection has been established
                WaitForDataConn()

                ' test to see if the data connection failed to be established sometime the active connection fails due to security settings on the ftp server
                If _dataConn Is Nothing Then
                    Throw New FtpDataConnectionException("Unable to establish a data connection to the destination.  The destination may have refused the connection.")
                End If

                ' create the data stream object - handles creation of SslStream and DeflateStream objects as well
                Dim conn As Stream = _dataConn.GetStream()

                ' test to see if we need to enable ssl/tls explicit mode
                If _securityProtocol <> FtpSecurityProtocol.None Then
                    conn = CreateSslStream(conn)
                End If

                ' test to see if we need to enable compression by using the DeflateStream
                If _isCompressionEnabled Then
                    conn = CreateZlibStream(direction, conn)
                End If

                ' based on the direction of the data transfer we need to handle the input and output streams
                Select Case direction
                    Case TransferDirection.ToClient
                        TransferBytes(conn, data, _maxDownloadSpeed * 1024)
                        Exit Select
                    Case TransferDirection.ToServer
                        TransferBytes(data, conn, _maxUploadSpeed * 1024)
                        Exit Select

                End Select
            Finally
                ' reset the transfer size used to calc % completed
                SetTransferSize(-1)
                ' attempt to close the data connection 
                CloseDataConn()
            End Try

            ' if no errors occurred and this is not a quoted command then we will wait for the server to send a closing connection message
            WaitForHappyCodes(FtpResponseCode.ClosingDataConnection)

            ' automatic integrity check with file transfer
            If _autoHashAlgorithm <> HashingAlgorithm.None AndAlso request.IsFileTransfer Then
                DoIntegrityCheck(request, data, restartPosition)
            End If
        End Sub

        Friend Function TransferText(request As FtpRequest) As String
            Dim output As Stream = New MemoryStream()
            TransferData(TransferDirection.ToClient, request, output)
            output.Position = 0
            Dim reader As New StreamReader(output, _encode)
            Return reader.ReadToEnd()
        End Function

        Friend Sub CompressionOn()
            If Not Features.Contains(FtpCmd.Mode, "Z") Then
                Throw New FtpCommandNotSupportedException("Cannot enable data compression.", "MODE Z")
            End If

            Try
                SendRequest(New FtpRequest(_encode, FtpCmd.Mode, "Z"))
            Catch ex As Exception
                Throw New FtpDataCompressionException("Unable to enable compression (MODE Z) on the destination.", ex)
            End Try
        End Sub

        Friend Sub CompressionOff()
            Try
                SendRequest(New FtpRequest(_encode, FtpCmd.Mode, "S"))
            Catch ex As Exception
                Throw New FtpDataCompressionException("Unable to disable compression (MODE S) on the destination.", ex)
            End Try
        End Sub

#End Region

#Region "Private Methods"

        Private Sub StartCommandMonitorThread()
            ' start the monitor thread which pumps FtpResponse objects on the FtpResponseQueue
            _responseMonitor = New Thread(New ThreadStart(AddressOf MonitorCommandConnection))
            _responseMonitor.Name = "FtpBase Response Monitor"
            _responseMonitor.Start()
        End Sub

        Private Function IsAsyncCancellationPending() As Boolean
            If _asyncWorker IsNot Nothing AndAlso _asyncWorker.CancellationPending Then
                _asyncCanceled = True
                Return True
            End If
            Return False
        End Function

        Private Sub TransferBytes(input As Stream, output As Stream, maxBytesPerSecond As Integer)
            Dim bytesTotal As Long = 0
            Dim bytesRead As Integer = 0
            Dim start As DateTime = DateTime.Now
            Dim elapsed As New TimeSpan(0)
            Dim bytesPerSec As Integer = 0
            Dim percentComplete As Integer = 0

            ' allocate space for buffer size if not done so already on a previous call
            ' or has been updated between calls
            If _dataBuffer Is Nothing OrElse _dataBuffer.Length <> _tcpBufferSize Then
                _dataBuffer = New Byte(_tcpBufferSize - 1) {}
            End If

            While True
                bytesRead = input.Read(_dataBuffer, 0, _tcpBufferSize)

                If bytesRead = 0 Then
                    Exit While
                End If

                bytesTotal += bytesRead
                output.Write(_dataBuffer, 0, bytesRead)

                ' calculate some statistics
                elapsed = DateTime.Now.Subtract(start)
                bytesPerSec = CInt(Math.Truncate(If(elapsed.TotalSeconds < 1, bytesTotal, bytesTotal / elapsed.TotalSeconds)))

                '  if the consumer subscribes to transfer progress event then fire it


                ' If TransferProgress IsNot Nothing Then
                If _transferSize > 0 Then
                    Dim newpc As Integer = CInt(Math.Truncate((CSng(bytesTotal) / CSng(_transferSize)) * 100))

                    ' to preserve resources and prevent a slow down of transfers due to
                    ' frequent event calls, only fire the event if the percentage has changed
                    If newpc <> percentComplete Then
                        percentComplete = newpc
                        RaiseEvent TransferProgress(Me, New TransferProgressEventArgs(bytesRead, bytesTotal, _transferSize, bytesPerSec, elapsed, percentComplete))
                    End If
                Else
                    ' always fire the event if we cannot calc percentages
                    RaiseEvent TransferProgress(Me, New TransferProgressEventArgs(bytesRead, bytesTotal, _transferSize, bytesPerSec, elapsed, percentComplete))
                End If
                '   End If

                ' test to see if this is an asychronous operation and if so make sure 
                ' the user has not requested the operation to be canceled
                If IsAsyncCancellationPending() Then
                    Throw New FtpAsynchronousOperationException("Asynchronous operation canceled by user.")
                End If

                ' throttle the transfer if necessary
                ThrottleByteTransfer(maxBytesPerSecond, bytesTotal, elapsed, bytesPerSec)
            End While



            '  if the consumer subscribes to transfer complete event then fire it
            RaiseEvent TransferComplete(Me, New TransferCompleteEventArgs(bytesTotal, bytesPerSec, elapsed))
        End Sub

        Private Sub ThrottleByteTransfer(maxBytesPerSecond As Integer, bytesTotal As Long, elapsed As TimeSpan, bytesPerSec As Integer)
            ' we only throttle if the maxBytesPerSecond is not zero (zero turns off the throttle)
            If maxBytesPerSecond > 0 Then
                ' we only throttle if our through-put is higher than what we want
                If bytesPerSec > maxBytesPerSecond Then
                    Dim elapsedMilliSec As Double = If(elapsed.TotalSeconds = 0, elapsed.TotalMilliseconds, elapsed.TotalSeconds * 1000)

                    ' need to calc a delay in milliseconds for the throttle wait based on how fast the 
                    ' transfer is relative to the speed it needs to be
                    Dim millisecDelay As Double = (bytesTotal \ (maxBytesPerSecond \ 1000) - elapsedMilliSec)

                    ' can only sleep to a max of an Int32 so we need to check this since bytesTotal is a long value
                    ' this should never be an issue but never say never
                    If millisecDelay > Int32.MaxValue Then
                        millisecDelay = Int32.MaxValue
                    End If

                    ' go to sleep
                    Thread.Sleep(CInt(Math.Truncate(millisecDelay)))
                End If
            End If
        End Sub

        Private Sub CreateCommandConn()
            If _host Is Nothing OrElse _host.Length = 0 Then
                Throw New FtpException("An FTP Host must be specified before opening connection to FTP destination.  Set the appropriate value using the Host property on the FtpClient object.")
            End If

            Try
                '  test to see if we should use the user supplied proxy object
                '  to create the connection
                If _proxy IsNot Nothing Then
                    _commandConn = _proxy.CreateConnection(_host, _port)
                Else
                    _commandConn = New TcpClient(_host, _port)
                End If
            Catch pex As ProxyException
                If _commandConn IsNot Nothing Then
                    _commandConn.Close()
                End If

                Throw New ProxyException([String].Format(CultureInfo.InvariantCulture, "A proxy error occurred while creating connection to FTP destination {0} on port {1}.", _host, _port.ToString(CultureInfo.InvariantCulture)), pex)
            Catch ex As Exception
                If _commandConn IsNot Nothing Then
                    _commandConn.Close()
                End If

                Throw New FtpConnectionOpenException([String].Format(CultureInfo.InvariantCulture, "An error occurred while opening a connection to FTP destination {0} on port {1}.", _host, _port.ToString(CultureInfo.InvariantCulture)), ex)
            End Try

            ' set command connection buffer sizes and timeouts
            _commandConn.ReceiveBufferSize = _tcpBufferSize
            _commandConn.ReceiveTimeout = _tcpTimeout
            _commandConn.SendBufferSize = _tcpBufferSize
            _commandConn.SendTimeout = _tcpTimeout

            ' set the command stream object
            _commandStream = _commandConn.GetStream()
        End Sub


        Private Sub CloseCommandConn()
            If _commandConn Is Nothing Then
                Return
            End If
            Try
                If _commandConn.Connected Then
                    '  send the quit command to the server
                    SendRequest(New FtpRequest(_encode, FtpCmd.Quit))
                End If
                _commandConn.Close()
            Catch
            End Try

            _commandConn = Nothing
        End Sub


        Private Sub WaitForHappyCodes(ParamArray happyResponseCodes As FtpResponseCode())
            WaitForHappyCodes(_commandTimeout, happyResponseCodes)
        End Sub

        ''' <summary>
        ''' Waits until a happy code has been returned by the FTP server or times out.
        ''' </summary>
        ''' <param name="timeout">Maximum time to wait before timing out.</param>
        ''' <param name="happyResponseCodes">Server response codes to wait for.</param>
        Protected Friend Sub WaitForHappyCodes(timeout As Integer, ParamArray happyResponseCodes As FtpResponseCode())
            _responseList.Clear()
            Do
                Dim response As FtpResponse = GetNextCommandResponse(timeout)
                _responseList.Add(response)
                RaiseServerResponseEvent(New FtpResponse(response))

                If Not response.IsInformational Then
                    If IsHappyResponse(response, happyResponseCodes) Then
                        Exit Do
                    End If

                    If IsUnhappyResponse(response) Then
                        _response = response
                        Throw New FtpResponseException("FTP command failed.", response)
                    End If
                End If
            Loop While True

            _response = _responseList.GetLast()
        End Sub

        Private Sub RaiseServerResponseEvent(response As FtpResponse)
            RaiseEvent ServerResponse(Me, New FtpResponseEventArgs(response))
        End Sub

        Private Sub RaiseConnectionClosedEvent()
            RaiseEvent ConnectionClosed(Me, New ConnectionClosedEventArgs())
        End Sub

        Private Function IsUnhappyResponse(response As FtpResponse) As Boolean
            If response.Code = FtpResponseCode.ServiceNotAvailableClosingControlConnection OrElse response.Code = FtpResponseCode.CannotOpenDataConnection OrElse response.Code = FtpResponseCode.ConnectionClosedSoTransferAborted OrElse response.Code = FtpResponseCode.RequestedFileActionNotTaken OrElse response.Code = FtpResponseCode.RequestedActionAbortedDueToLocalErrorInProcessing OrElse response.Code = FtpResponseCode.RequestedActionNotTakenInsufficientStorage OrElse response.Code = FtpResponseCode.SyntaxErrorCommandUnrecognized OrElse response.Code = FtpResponseCode.SyntaxErrorInParametersOrArguments OrElse response.Code = FtpResponseCode.CommandNotImplemented OrElse response.Code = FtpResponseCode.BadSequenceOfCommands OrElse response.Code = FtpResponseCode.CommandNotImplementedForThatParameter OrElse response.Code = FtpResponseCode.NotLoggedIn OrElse response.Code = FtpResponseCode.NeedAccountForStoringFiles OrElse response.Code = FtpResponseCode.RequestedActionNotTakenFileUnavailable OrElse response.Code = FtpResponseCode.RequestedActionAbortedPageTypeUnknown OrElse response.Code = FtpResponseCode.RequestedFileActionAbortedExceededStorageAllocation OrElse response.Code = FtpResponseCode.RequestedActionNotTakenFileNameNotAllowed Then
                Return True
            Else
                Return False
            End If
        End Function

        Private Function IsHappyResponse(response As FtpResponse, happyResponseCodes As FtpResponseCode()) As Boolean
            ' always return true if there are no responses to validate
            If happyResponseCodes.Length = 0 Then
                Return True
            End If

            For j As Integer = 0 To happyResponseCodes.Length - 1
                If happyResponseCodes(j) = response.Code Then
                    Return True
                End If
            Next
            Return False
        End Function

        Private Sub MonitorCommandConnection()
            Dim buffer As Byte() = New Byte(_tcpBufferSize - 1) {}
            Dim response As New StringBuilder()
            Dim code421Detected As Boolean = False

            While IsConnected
                SyncLock _reponseMonitorLock
                    Thread.Sleep(WAIT_FOR_COMMAND_RESPONSE_INTERVAL)
                    Try
                        If _commandConn IsNot Nothing AndAlso _commandConn.GetStream().DataAvailable Then
                            Dim bytes As Integer = _commandStream.Read(buffer, 0, _tcpBufferSize)
                            Dim [partial] As String = _encode.GetString(buffer, 0, bytes)
                            response.Append([partial])

                            ' write out the debug response
                            Debug.Write("R: " & [partial])

                            If Not [partial].EndsWith(vbLf) Then
                                Continue While
                            End If

                            '  parse out the response code sent back from the server
                            '  in some cases more than one response can be sent with
                            '  each line separated with a crlf pair.
                            Dim responseArray As String() = SplitResponse(response.ToString())
                            For i As Integer = 0 To responseArray.Length - 1
                                Dim r As New FtpResponse(responseArray(i))
                                _responseQueue.Enqueue(r)
                                ' if a 421 response code is detected then the server has closed the connection
                                If r.Code = FtpResponseCode.ServiceNotAvailableClosingControlConnection Then
                                    code421Detected = True
                                End If
                            Next

                            response.Remove(0, response.Length)

                            ' if code 421 was detected then close the command connection
                            If code421Detected Then
                                CloseCommandConn()
                            End If
                        End If
                    Catch
                    End Try
                End SyncLock
            End While

            RaiseConnectionClosedEvent()

        End Sub

        Private Function GetNextCommandResponse(timeout As Integer) As FtpResponse
            Dim sleepTime As Integer = 0
            While _responseQueue.Count = 0
                If Not IsConnected Then
                    Throw New FtpConnectionClosedException("Connection is closed.")
                End If

                If IsAsyncCancellationPending() Then
                    Throw New FtpAsynchronousOperationException("Asynchronous operation canceled.")
                End If

                Thread.Sleep(WAIT_FOR_DATA_INTERVAL)
                sleepTime += WAIT_FOR_DATA_INTERVAL
                ' if we timed out make and no responses have come in then throw an exception
                If sleepTime > timeout AndAlso _responseQueue.Count = 0 Then
                    Throw New FtpCommandResponseTimeoutException("A timeout occurred while waiting for the destination to send a response.  The last reponse from the destination is '" & Convert.ToString(_response.Text) & "'")
                End If
            End While

            ' return next response object from the queue
            Return _responseQueue.Dequeue()
        End Function

        Private Function SplitResponse(response As String) As String()
            Return response.Split(New Char() {ControlChars.Cr, ControlChars.Lf}, StringSplitOptions.RemoveEmptyEntries)
        End Function

        Private Sub CreateActiveConn()
            ' retrieve local IPv4 or IPv6 address from local host based on user's settings
            ' and available IP addresses on the local host; otherwise set to a user specific value
            Dim ip As IPAddress = GetClientIPAddress()

            ' set the event to nonsignaled state
            _activeSignal.Reset()

            Dim localHostName As String = Dns.GetHostName()

            ' using the IP Address find and open an active listener port on the local host
            ' this method will use an Async callback on the listern socket to accept the connection
            ' and subsequently Set the _activeSignal object to a signaled state
            Dim port As Integer = OpenActiveListenerPort(localHostName, ip)

            ' build the port information string
            Dim portInfo As String = BuildPortInfo(port, ip)

            Try
                SendPortRequest(portInfo)
            Catch fex As FtpException
                Throw New FtpDataConnectionException([String].Format("An error occurred while issuing active port command '{0}' on an active FTP connection.", portInfo), fex)
            End Try
        End Sub

        Private Function GetNextActivePort() As Integer
            If _activePort < _activePortRangeMin OrElse _activePort > _activePortRangeMax Then
                _activePort = _activePortRangeMin
            Else
                _activePort += 1
            End If

            Return _activePort
        End Function

        Private Sub SendPortRequest(portInfo As String)
            ' if IPv6 is selected then send EPRT request else send PORT request
            Select Case _networkProtocol
                Case NetworkVersion.IPv4
                    SendRequest(New FtpRequest(_encode, FtpCmd.Port, portInfo))
                    Exit Select
                Case NetworkVersion.IPv6
                    SendRequest(New FtpRequest(_encode, FtpCmd.Eprt, portInfo))
                    Exit Select
                Case Else
                    Throw New FtpException("unexpected network protocol version")
            End Select
        End Sub

        Private Function BuildPortInfo(port As Integer, ip As IPAddress) As String
            Select Case _networkProtocol
                Case NetworkVersion.IPv4
                    Return BuildIPv4PortInfo(port, ip)
                Case NetworkVersion.IPv6
                    Return BuildIPv6PortInfo(port, ip)
                Case Else
                    Throw New FtpException("unexpected network protocol version")
            End Select
        End Function

        Private Function BuildIPv4PortInfo(port As Integer, ip As IPAddress) As String
            Dim ipbytes As Byte() = ip.GetAddressBytes()
            Dim portInfo As String = [String].Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3},{4},{5}", ipbytes(0).ToString(CultureInfo.InvariantCulture), ipbytes(1).ToString(CultureInfo.InvariantCulture), ipbytes(2).ToString(CultureInfo.InvariantCulture), ipbytes(3).ToString(CultureInfo.InvariantCulture), _
                port \ 256, port Mod 256)
            Return portInfo
        End Function

        Private Function BuildIPv6PortInfo(port As Integer, ip As IPAddress) As String
            ' (<d><net-prt><d><net-addr><d><tcp-port><d>)
            ' net-prt:  1 - IPv4 or  2 - IPv6 (required)
            ' net-addr:  network address IPv4 or IPv6 (required)
            ' tcp-port:  listening port of FTP server (required)
            '
            ' Example:
            '      Client> EPRT (|2|1080::8:800:200C:417A|5282|)
            '

            Dim netprt As String = "2"
            Dim addr As String = ip.ToString()
            ' remove the ipv6 scope identifer value if it is represented in ipv6 string
            If addr.IndexOf("%"c) <> -1 Then
                addr = addr.Substring(0, addr.IndexOf("%"c))
            End If

            Dim tcpport As String = port.ToString(CultureInfo.InvariantCulture)
            Dim portInfo As String = [String].Format(CultureInfo.InvariantCulture, "|{0}|{1}|{2}|", netprt, addr, tcpport)
            Return portInfo
        End Function

        Private Function GetClientIPAddress() As IPAddress
            ' if the user has specified a specific client IP address to 
            ' use then use that address; otherwise obtain one from the local machine.
            If Not [String].IsNullOrEmpty(_client) Then
                Dim ip As IPAddress = IPAddress.Parse(_client)
                If (ip.AddressFamily = AddressFamily.InterNetwork AndAlso _networkProtocol <> NetworkVersion.IPv4) OrElse (ip.AddressFamily = AddressFamily.InterNetworkV6 AndAlso _networkProtocol <> NetworkVersion.IPv6) Then
                    Throw New FtpDataConnectionException("The Client IP address supplied does not match the NetworkProtocol selected.  Please check the NetworkProtcol property setting on the FtpClient.")
                End If
                Return ip
            End If

            Select Case _networkProtocol
                Case NetworkVersion.IPv4
                    Return GetClientIPv4Address()
                Case NetworkVersion.IPv6
                    Return GetClientIPv6Address()
                Case Else
                    Throw New FtpException("unexpected network protocol version")
            End Select
        End Function

        ''' <summary>
        ''' This method is used for obtaining the IPv4 IPAddress object for FTP active
        ''' connection (PORT).
        ''' </summary>
        ''' <return></return>
        Private Function GetClientIPv4Address() As IPAddress
            Dim localHostName As String = Dns.GetHostName()
            Dim list As IPAddress() = Dns.GetHostAddresses(localHostName)
            Dim ip As IPAddress = Nothing
            For Each a As IPAddress In list
                ' look for IPv4 addresses only (InterNetwork)
                If a.AddressFamily = AddressFamily.InterNetwork Then
                    ip = a
                    Exit For
                End If
            Next
            If ip Is Nothing Then
                Throw New Exception("could not find a IPv4 address on local host")
            End If
            Return ip
        End Function

        ''' <summary>
        ''' This method is used for obtaining the IPv6 IPAddress object for FTP active
        ''' connection (EPRT).
        ''' </summary>
        ''' <return></return>
        Private Function GetClientIPv6Address() As IPAddress
            Dim localHostName As String = Dns.GetHostName()
            Dim list As IPAddress() = Dns.GetHostAddresses(localHostName)
            Dim ip As IPAddress = Nothing
            For Each a As IPAddress In list
                ' look for IPv6 addresses only (InterNetworkV6)
                If a.AddressFamily = AddressFamily.InterNetworkV6 Then
                    ip = a
                    Exit For
                End If
            Next
            If ip Is Nothing Then
                Throw New Exception("could not find a IPv6 address on local host")
            End If
            Return ip

        End Function

        Private Function OpenActiveListenerPort(localHost As String, localAddr As IPAddress) As Integer
            Dim success As Boolean = False
            Dim listenerPort As Integer = 0

            Do
                Dim failureCnt As Integer = 0
                Try
                    listenerPort = GetNextActivePort()
                    _activeListener = New TcpListener(localAddr, listenerPort)
                    _activeListener.Start()
                    success = True
                Catch socketError As SocketException
                    ' test to see if the socket is no good
                    If socketError.ErrorCode = 10048 AndAlso System.Threading.Interlocked.Increment(failureCnt) < (_activePortRangeMax - _activePortRangeMin) Then
                        _activeListener.[Stop]()
                    Else
                        Throw New FtpDataConnectionException([String].Format(CultureInfo.InvariantCulture, "An error occurred while trying to create an active connection on host {0} port {1}", localHost, listenerPort.ToString(CultureInfo.InvariantCulture)), socketError)
                    End If
                End Try
            Loop While Not success

            ' begin accepting tcp/ip data from our newly created listener port
            ' data is accepted by the async AcceptTcpClientCallback method
            _activeListener.BeginAcceptTcpClient(New AsyncCallback(AddressOf Me.AcceptTcpClientCallback), _activeListener)

            Return listenerPort
        End Function

        ' async callback that occurs once the server has connected to the client listener data connection
        Private Sub AcceptTcpClientCallback(ar As IAsyncResult)
            ' Get the listener that handles the client request.
            Dim listener As TcpListener = DirectCast(ar.AsyncState, TcpListener)

            ' make sure that the server didn't close the connection on us or just refuse to allow an active connection
            ' for security considerations and other purposes some servers will refuse active connections
            Try
                _dataConn = listener.EndAcceptTcpClient(ar)
                ' should we catch this and do something?
            Catch
            End Try

            ' signal the calling thread to continue now that the data connection is open
            _activeSignal.[Set]()
        End Sub

        Private Sub OpenDataConn()
            '  create the approiate ftp data connection depending on how the ftp client should send 
            '  or receive data from the ftp server
            If _dataTransferMode = TransferMode.Active Then
                CreateActiveConn()
            Else
                CreatePassiveConn()
            End If
        End Sub

        Private Sub CloseDataConn()
            '  close the tcpclient data connection object
            If _dataConn IsNot Nothing Then
                Try
                    _dataConn.Close()
                Catch
                End Try
                _dataConn = Nothing
            End If

            '  stop the tcplistner object if we used it for an active data transfer where we
            '  are listing and the server makes a connection to the client and pushed data 
            If _dataTransferMode = TransferMode.Active AndAlso _activeListener IsNot Nothing Then
                Try
                    _activeListener.[Stop]()
                Catch
                End Try
                _activeListener = Nothing
            End If
        End Sub

        Private Sub WaitForDataConn()
            '  if the transfer mode is active then we have to open a listener and wait for the server to connect before sending
            '  or receiving data but if the transfer mode is passive then we make the connection (non blocking) 
            '  and therefore there is no need to wait for the server and our signal that the server has connected 
            '  - we already have the connection object since the tcpclient object blocks until the server accepts the connection
            If _dataTransferMode = TransferMode.Active Then
                ' wait until a data connection is made before continuing based on a thread blocking signal object
                If Not _activeSignal.WaitOne(_transferTimeout, False) Then
                    If _response.Code = FtpResponseCode.CannotOpenDataConnection Then
                        Throw New FtpDataConnectionException([String].Format(CultureInfo.InvariantCulture, "The ftp destination was unable to open a data connection to the ftp client on port {0}.", _activePort))
                    Else
                        Throw New FtpDataConnectionTimeoutException("The data connection timed out waiting for data to transfer from the destination.")
                    End If
                End If
            Else
                Return
            End If
        End Sub

      Private Sub CreatePassiveConn()
            Dim host As String
            Dim port As Integer

            Try

                SendPassiveCmd(host, port)

                '  create a new tcp client object and use proxy if supplied
                If _proxy IsNot Nothing Then
                    _dataConn = _proxy.CreateConnection(host, port)
                Else
                    _dataConn = New TcpClient(host, port)
                End If

                _dataConn.ReceiveBufferSize = _tcpBufferSize
                _dataConn.ReceiveTimeout = _tcpTimeout
                _dataConn.SendBufferSize = _tcpBufferSize
                _dataConn.SendTimeout = _tcpTimeout

            Catch ex As Exception
                Throw New FtpDataConnectionException([String].Format(CultureInfo.InvariantCulture, "An error occurred while opening passive data connection to destination '{0}' on port '{1}'.", host, port), ex)
            End Try
        End Sub

        ' get the IP Addresses for the connected host
        Private Function GetHostIpAddresses() As IPAddress()
            If _hostIpAddresses Is Nothing Then
                Dim hostEntry = Dns.GetHostEntry(_host)
                _hostIpAddresses = hostEntry.AddressList
            End If
            Return _hostIpAddresses
        End Function


        Private Shared Function IsIpAddressLocal(ipAddress As String) As Boolean
            Dim regex As Regex = New Regex("(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)|(^127\.0\.0\.1)")
            Dim match As Match = regex.Match(ipAddress)
            Return match.Success
        End Function


        Private Sub SendPassiveCmd(ByRef host As String, ByRef port As Integer)

            Select Case _networkProtocol

                Case NetworkVersion.IPv4

                    Try

                        _hostIpAddresses = GetHostIpAddresses()

                        For Each _ip In _hostIpAddresses
                            If _ip.GetAddressBytes.Length = 4 Then
                                host = _ip.ToString
                                Exit For
                            End If
                        Next

                    Catch ex As Exception
                        Debug.WriteLine("Error Getting Host IP Addresses! You may Have Problems if the Server is using a Local IP-Adress!")
                    End Try

                    SendPasvCmd(host, port)

                    Exit Select

                Case NetworkVersion.IPv6

                    SendEpsvCmd(host, port)
                    Exit Select
                Case Else
                    Throw New FtpException("unexpected network protocol version")
            End Select

        End Sub

        ''' <summary>
        ''' Send the Extended Passive Mode (EPSV) command to the FTP host server.
        ''' </summary>
        ''' <remarks>
        ''' The EPSV command tells the server to enter a passive FTP session rather than Active. 
        ''' (Its use is required for IPv6.) This allows users behind routers/firewalls to connect 
        ''' over FTP when they might not be able to connect over an Active (PORT/EPRT) FTP session. 
        ''' EPSV mode has the server tell the client where to connect for the data port on the server.
        ''' </remarks>
        ''' <param name="host">Host name returned by the FTP server or the default host name.</param>
        ''' <param name="port">Port number to connect to returned by FTP server.</param>
        Private Sub SendEpsvCmd(ByRef host As String, ByRef port As Integer)
            '  send command to get passive port to be used from the server
            '  using the extended port command which works with IPv4 and IPv6
            '  and is defined in RFC 2428
            Try
                ' EPSV<sp><opt-tcp-port-request>
                SendRequest(New FtpRequest(_encode, FtpCmd.Epsv))
            Catch fex As FtpException
                Throw New FtpDataConnectionException("An error occurred while issuing up a passive FTP connection command.", fex)
            End Try

            ' (<d><net-prt><d><net-addr><d><tcp-port><d>)
            ' net-prt:  1 - IPv4 or  2 - IPv6 (optional)
            ' net-addr:  network address IPv4 or IPv6 (optional)
            ' tcp-port:  listening port of FTP server (required)
            '
            ' Example 1:
            '      Client> EPSV
            '      Server> 229 Entering Extended Passive Mode (|1|132.235.1.2|63643|)
            '
            ' Example 2:
            '      Client> EPSV
            '      Server> 229 Entering Extended Passive Mode (|2|1080::8:800:200C:417A|5282|)
            '
            ' Example 3:
            '      Client> EPSV
            '      Server> 229 Entering Extended Passive Mode (|||3282|)
            Dim r As String = _response.Text
            Dim startIdx As Integer = r.IndexOf("(") + 1
            Dim endIdx As Integer = r.IndexOf(")")

            '  parse the transfer connection data from the ftp server response
            Dim data As String() = r.Substring(startIdx, endIdx - startIdx).Split("|"c)

            ' build the data host name from the server response
            ' if one is supplied, otherwise use connected host info
            If data(2).Length <> 0 Then
                host = data(2)
            Else
                host = _host
            End If
            ' extract and convert the port number from the server response
            port = Int32.Parse(data(3), CultureInfo.InvariantCulture)
        End Sub

        ''' <summary>
        ''' Send the Passive Mode (PASV) command to the FTP host server.
        ''' </summary>
        ''' <remarks>
        ''' This command tells the server to enter a passive FTP session rather than Active. 
        ''' This allows users behind routers/firewalls to connect over FTP when they might not 
        ''' be able to connect over an Active (PORT) FTP session. PASV mode has the server tell 
        ''' the client where to connect for the data port on the server.  PASV mode only works
        ''' with IPv4.  The Extended Passive Mode command must be used for IPv6 FTP host addresses.
        ''' </remarks>
        ''' <param name="host">Host name returned by the FTP server or the default host name.</param>
        ''' <param name="port">Port number to connect to returned by FTP server.</param>
        Private Sub SendPasvCmd(ByRef host As String, ByRef port As Integer)
            '  send command to get passive port to be used from the server
            Try
                SendRequest(New FtpRequest(_encode, FtpCmd.Pasv))
            Catch fex As FtpException
                Throw New FtpDataConnectionException("An error occurred while issuing up a passive FTP connection command.", fex)
            End Try

            '  get the port on the end
            '  to calculate the port number we must multiple the 5th value by 256 and then add the 6th value
            '  example:  
            '       Client> PASV
            '       Server> 227 Entering Passive Mode (123,45,67,89,158,26)
            '  In the example of PASV mode the server has said it will be listening on IP address 123.45.67.89 
            '  on TCP port 40474 for the data channel. (Note: the destinationPort is the 158,26 pair and is: 158x256 + 26 = 40474).

            '  get the begin and end positions to extract data from the response string
            Dim r As String = _response.Text
            Dim startIdx As Integer = r.IndexOf("(") + 1
            Dim endIdx As Integer = r.IndexOf(")")

            '  parse the transfer connection data from the ftp server response
            Dim data As String() = r.Substring(startIdx, endIdx - startIdx).Split(","c)

            ' build the data host name from the server response
            ' host = data(0) & "." & data(1) & "." & data(2) & "." & data(3)

            If IsIpAddressLocal(data(0) & "." & data(1) & "." & data(2) & "." & data(3)) Then
                Debug.WriteLine("Private IP - Using Server IP Address!")
                host = host.Split(CChar("."))(0) & "." & host.Split(CChar("."))(1) & "." & host.Split(CChar("."))(2) & "." & host.Split(CChar("."))(3)
            Else
                host = data(0) & "." & data(1) & "." & data(2) & "." & data(3)
            End If

            ' extract and convert the port number from the server response
            port = Int32.Parse(data(4), CultureInfo.InvariantCulture) * 256 + Int32.Parse(data(5), CultureInfo.InvariantCulture)

        End Sub

        ''' <summary>
        ''' Creates an SSL or TLS secured stream.
        ''' </summary>
        ''' <param name="stream">Unsecured stream.</param>
        ''' <return>Secured stream</return>
        Private Function CreateSslStream(stream As Stream) As Stream
            ' create an SSL or TLS stream that will close the client's stream
            Dim ssl As New SslStream(stream, True, New RemoteCertificateValidationCallback(AddressOf secureStream_ValidateServerCertificate), Nothing)

            ' choose the protocol
            Dim protocol As SslProtocols = SslProtocols.None
            Select Case _securityProtocol
                Case FtpSecurityProtocol.Tls1OrSsl3Explicit, FtpSecurityProtocol.Tls1OrSsl3Implicit
                    protocol = SslProtocols.[Default]
                    Exit Select
                Case FtpSecurityProtocol.Ssl2Explicit, FtpSecurityProtocol.Ssl2Implicit
                    protocol = SslProtocols.Ssl2
                    Exit Select
                Case FtpSecurityProtocol.Ssl3Explicit, FtpSecurityProtocol.Ssl3Implicit
                    protocol = SslProtocols.Ssl3
                    Exit Select
                Case FtpSecurityProtocol.Tls1Explicit, FtpSecurityProtocol.Tls1Implicit
                    protocol = SslProtocols.Tls
                    Exit Select
                Case Else
                    Throw New FtpSecureConnectionException([String].Format("unexpected FtpSecurityProtocol type '{0}'", _securityProtocol.ToString()))
            End Select

            ' note: the server name must match the name on the server certificate.
            Try
                ' authenticate the client
                ssl.AuthenticateAsClient(_host, _clientCertificates, protocol, True)
            Catch authEx As AuthenticationException
                Throw New FtpAuthenticationException("Secure FTP session certificate authentication failed.", authEx)
            End Try

            Return ssl
        End Function

        ' the following method is invoked by the RemoteCertificateValidationDelegate.
        Private Function secureStream_ValidateServerCertificate(sender As Object, certificate As X509Certificate, chain As X509Chain, sslPolicyErrors__1 As SslPolicyErrors) As Boolean
            ' if the option is set to always accept the certificate then do not fire the validate certificate event
            If _alwaysAcceptServerCertificate Then
                Return True
            End If

            ' if it is the same certificate we have already approved then don't validate it again
            If _serverCertificate IsNot Nothing AndAlso certificate.GetCertHashString() = _serverCertificate.GetCertHashString() Then
                Return True
            End If

            ' invoke the ValidateServerCertificate event if the user is subscribing to it
            ' ignore our own logic and let the user decide if the certificate is valid or not
            'If ValidateServerCertificate IsNot Nothing Then
            '	Dim args As New ValidateServerCertificateEventArgs(New X509Certificate2(certificate.GetRawCertData()), chain, sslPolicyErrors__1)
            '	RaiseEvent ValidateServerCertificate(Me, args)
            '	' make a copy of the certificate due to sharing violations
            '	If args.IsCertificateValid Then
            '		_serverCertificate = New X509Certificate2(certificate.GetRawCertData())
            '	End If
            '	Return args.IsCertificateValid
            'Else
            '	' analyze the policy errors and decide if the certificate should be accepted or not.
            '	If (sslPolicyErrors__1 And SslPolicyErrors.RemoteCertificateNameMismatch) = SslPolicyErrors.RemoteCertificateNameMismatch Then
            '		Throw New FtpCertificateValidationException([String].Format("Certificate validation failed.  The host name '{0}' does not match the name on the security certificate '{1}'.  To override this behavior, subscribe to the ValidateServerCertificate event to validate certificates.", _host, certificate.Issuer))
            '	End If

            '	If sslPolicyErrors__1 = SslPolicyErrors.None OrElse (sslPolicyErrors__1 And SslPolicyErrors.RemoteCertificateChainErrors) = SslPolicyErrors.RemoteCertificateChainErrors Then
            '		' make a copy of the server certificate due to sharing violations
            '		_serverCertificate = New X509Certificate2(certificate.GetRawCertData())
            '		Return True
            '	Else
            '		Return False
            '	End If
            'End If
            Try
                Dim args As New ValidateServerCertificateEventArgs(New X509Certificate2(certificate.GetRawCertData()), chain, sslPolicyErrors__1)
                RaiseEvent ValidateServerCertificate(Me, args)
                ' make a copy of the certificate due to sharing violations
                If args.IsCertificateValid Then
                    _serverCertificate = New X509Certificate2(certificate.GetRawCertData())
                End If
                Return args.IsCertificateValid
            Catch
                ' analyze the policy errors and decide if the certificate should be accepted or not.
                If (sslPolicyErrors__1 And SslPolicyErrors.RemoteCertificateNameMismatch) = SslPolicyErrors.RemoteCertificateNameMismatch Then
                    Throw New FtpCertificateValidationException(String.Format("Certificate validation failed.  The host name '{0}' does not match the name on the security certificate '{1}'.  To override this behavior, subscribe to the ValidateServerCertificate event to validate certificates.", _host, certificate.Issuer))
                End If

                If sslPolicyErrors__1 = SslPolicyErrors.None OrElse (sslPolicyErrors__1 And SslPolicyErrors.RemoteCertificateChainErrors) = SslPolicyErrors.RemoteCertificateChainErrors Then
                    ' make a copy of the server certificate due to sharing violations
                    _serverCertificate = New X509Certificate2(certificate.GetRawCertData())
                    Return True
                Else
                    Return False
                End If
            End Try


        End Function

        Private Sub CreateSslExplicitCommandStream()
            Try
                ' send authentication type request
                Dim authCommand As String = ""
                Select Case _securityProtocol
                    Case FtpSecurityProtocol.Tls1OrSsl3Explicit, FtpSecurityProtocol.Ssl3Explicit, FtpSecurityProtocol.Ssl2Explicit
                        authCommand = "SSL"
                        Exit Select
                    Case FtpSecurityProtocol.Tls1Explicit
                        authCommand = "TLS"
                        Exit Select
                End Select

                Debug.Assert(authCommand.Length > 0, "auth command should have a value - make sure every enum option in auth command has a corresponding value")

                SendRequest(New FtpRequest(_encode, FtpCmd.Auth, authCommand))

                ' set the active command stream to the ssl command stream object
                SyncLock _reponseMonitorLock
                    _commandStream = CreateSslStream(_commandConn.GetStream())
                End SyncLock

                SendRequest(New FtpRequest(_encode, FtpCmd.Pbsz, "0"))
                SendRequest(New FtpRequest(_encode, FtpCmd.Prot, "P"))
            Catch fauth As FtpAuthenticationException
                Throw New FtpSecureConnectionException([String].Format("An ftp authentication exception occurred while setting up a explicit ssl/tls command stream.  {0}", fauth.Message), _response, fauth)
            Catch fex As FtpException
                Throw New FtpSecureConnectionException([String].Format("An error occurred while setting up a explicit ssl/tls command stream.  {0}", fex.Message), _response, fex)
            End Try

        End Sub

        Private Sub CreateSslImplicitCommandStream()
            Try
                ' set the active command stream to the ssl command stream object
                SyncLock _reponseMonitorLock
                    _commandStream = CreateSslStream(_commandConn.GetStream())
                End SyncLock
            Catch fauth As FtpAuthenticationException
                Throw New FtpSecureConnectionException([String].Format("An ftp authentication exception occurred while setting up a implicit ssl/tls command stream.  {0}", fauth.Message), _response, fauth)
            Catch fex As FtpException
                Throw New FtpSecureConnectionException([String].Format("An error occurred while setting up a implicit ssl/tls command stream.  {0}", fex.Message), _response, fex)
            End Try

        End Sub

        Private Sub DoIntegrityCheck(request As FtpRequest, stream As Stream, restartPosition As Long)
            ' get the file path from the request argument
            Dim path As String = request.Arguments(0)
            Dim startPos As Long = restartPosition
            Dim endPos As Long = stream.Length

            Dim streamHash As String = ComputeHash(_autoHashAlgorithm, stream, startPos)
            Dim serverHash As String = GetHash(_autoHashAlgorithm, path, startPos, endPos)

            streamHash = streamHash.ToLower()
            serverHash = serverHash.ToLower()

            ' string compare the dataHash to the server hash value and see if they are the same
            If serverHash.IndexOf(streamHash, 0) = -1 Then
                Throw New FtpHashingException([String].Format("File integrity check failed.  The destination hash value '{0}' for the file '{1}' did not match the data transfer hash value '{2}'.", serverHash, path, streamHash))
            End If

            'string compare the dataHash to the server hash value and see if they are the same
            'if (String.Compare(streamHash, serverHash, StringComparison.InvariantCultureIgnoreCase) != 0)
            '    throw new FtpHashingException(String.Format("File integrity check failed.  The destination hash value '{0}' for the file '{1}' did not match the data transfer hash value '{2}'.", serverHash, path, streamHash));
        End Sub

        Private Sub DontWaitForHappyCodes()
            If _responseQueue.Count = 0 Then
                Return
            End If

            _responseList.Clear()
            While _responseQueue.Count > 0
                Dim response As FtpResponse = _responseQueue.Dequeue()
                _responseList.Add(response)
                RaiseServerResponseEvent(New FtpResponse(response))
            End While
            _response = _responseList.GetLast()
        End Sub

        Private Function CreateZlibStream(direction As TransferDirection, stream As Stream) As Stream
            Dim zstream As DeflateStream = Nothing

            Select Case direction
                Case TransferDirection.ToClient
                    zstream = New DeflateStream(stream, CompressionMode.Decompress, True)
                    ' zlib fix to ignore first two bytes of header data 
                    zstream.BaseStream.ReadByte()
                    zstream.BaseStream.ReadByte()
                    Exit Select

                Case TransferDirection.ToServer
                    zstream = New DeflateStream(stream, CompressionMode.Compress, True)
                    ' this is a fix for the DeflateStream class only when sending compressed data to the server.  
                    ' Zlib has two bytes of data attached to the header that we have to write before processing the data stream.
                    zstream.BaseStream.WriteByte(120)
                    zstream.BaseStream.WriteByte(218)
                    Exit Select
            End Select

            Return zstream
        End Function

        Private Function GetHashFeatureArgument(algo As HashingAlgorithm) As FtpFeatureArgument
            Dim argText As String = ConvertHashAlgoToTextArg(algo)

            Dim f As FtpFeature = Features(FtpCmd.Hash)
            If f Is Nothing Then
                Throw New FtpCommandNotSupportedException("Cannot find hash feature.", FtpCmd.Hash)
            End If

            Dim fa As FtpFeatureArgument = f.Arguments(argText)
            If fa Is Nothing Then
                Throw New FtpFeatureException([String].Format("The HASH feature argument '{0}' is not supported by the FTP server.", argText))
            End If

            Return fa
        End Function

        Private Function ConvertHashXAlgoToTextCmd(hash As HashingAlgorithm) As FtpCmd
            Select Case hash
                Case HashingAlgorithm.Crc32
                    Return FtpCmd.Xcrc
                Case HashingAlgorithm.Md5
                    Return FtpCmd.Xmd5
                Case HashingAlgorithm.Sha1
                    Return FtpCmd.Xsha1
                Case HashingAlgorithm.Sha256
                    Return FtpCmd.Xsha256
                Case HashingAlgorithm.Sha512
                    Return FtpCmd.Xsha512
                Case Else
                    Throw New FtpException("unsupported hash option")
            End Select
        End Function

        Private Function ConvertHashAlgoToTextArg(hash As HashingAlgorithm) As String
            Select Case hash
                Case HashingAlgorithm.Crc32
                    Return HASH_CRC_32
                Case HashingAlgorithm.Md5
                    Return HASH_MD5
                Case HashingAlgorithm.Sha1
                    Return HASH_SHA_1
                Case HashingAlgorithm.Sha256
                    Return HASH_SHA_256
                Case HashingAlgorithm.Sha512
                    Return HASH_SHA_512
                Case Else
                    Throw New FtpException("unsupported hash option")
            End Select
        End Function

        Friend Function IsTranferProgressEventSet() As Boolean
            Return True
        End Function

        Friend Sub SetTransferSize(size As Long)
            _transferSize = size
        End Sub

#End Region

#Region "Destructors"

        ''' <summary>
        ''' Disposes all objects and connections.
        ''' </summary>
        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub


        ''' <summary>
        ''' Dispose Method.
        ''' </summary>
        ''' <param name="disposing"></param>
        Protected Overridable Sub Dispose(disposing As Boolean)
            If disposing Then
                If _asyncWorker IsNot Nothing AndAlso _asyncWorker.IsBusy Then
                    _asyncWorker.CancelAsync()
                End If

                If _activeListener IsNot Nothing Then
                    _activeListener.[Stop]()
                End If

                If _dataConn IsNot Nothing AndAlso _dataConn.Connected Then
                    _dataConn.Close()
                End If

                If _commandConn IsNot Nothing AndAlso _commandConn.Connected Then
                    _commandConn.Close()
                End If

                If _activeSignal IsNot Nothing Then
                    _activeSignal.Close()
                End If

                If _responseMonitor IsNot Nothing AndAlso _responseMonitor.IsAlive Then
                    _responseMonitor.Abort()
                End If
            End If
        End Sub

        ''' <summary>
        ''' Dispose deconstructor.
        ''' </summary>
        Protected Overrides Sub Finalize()
            Try
                Dispose(False)
            Finally
                MyBase.Finalize()
            End Try
        End Sub

#End Region
    End Class
End Namespace
